import { exec, execSync } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import {
  NftBaseError,
  NftValidationError,
  NftExecutionError,
  NftResourceError
} from './models/index.js';
import type {
  PortRange,
  NfTableProxyOptions,
  NfTablesStatus
} from './models/index.js';

const execAsync = promisify(exec);

/**
 * Represents a rule added to nftables
 */
interface NfTablesRule {
  handle?: number;       // Rule handle for deletion
  tableFamily: string;   // 'ip' or 'ip6'
  tableName: string;     // Table name
  chainName: string;     // Chain name
  ruleContents: string;  // Rule definition
  added: boolean;        // Whether the rule was successfully added
  verified?: boolean;    // Whether the rule has been verified as applied
}

/**
 * NfTablesProxy sets up nftables NAT rules to forward TCP traffic.
 * Enhanced with multi-port support, IPv6, connection tracking, metrics,
 * and more advanced features.
 */
export class NfTablesProxy {
  public settings: NfTableProxyOptions;
  private rules: NfTablesRule[] = [];
  private ipSets: Map<string, string[]> = new Map(); // Store IP sets for tracking
  private ruleTag: string;
  private tableName: string;
  private tempFilePath: string;
  private static NFT_CMD = 'nft';

  constructor(settings: NfTableProxyOptions) {
    // 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,
      tableName: settings.tableName || 'portproxy',
      logFormat: settings.logFormat || 'plain',
      useIPSets: settings.useIPSets !== undefined ? settings.useIPSets : true,
      maxRetries: settings.maxRetries || 3,
      retryDelayMs: settings.retryDelayMs || 1000,
      useAdvancedNAT: settings.useAdvancedNAT !== undefined ? settings.useAdvancedNAT : false,
    };
    
    // Generate a unique identifier for the rules added by this instance
    this.ruleTag = `NfTablesProxy:${Date.now()}:${Math.random().toString(36).substr(2, 5)}`;
    
    // Set table name
    this.tableName = this.settings.tableName || 'portproxy';
    
    // Create a temp file path for batch operations
    this.tempFilePath = path.join(os.tmpdir(), `nft-rules-${Date.now()}.nft`);

    // Register cleanup handlers if deleteOnExit is true
    if (this.settings.deleteOnExit) {
      const cleanup = () => {
        try {
          this.stopSync();
        } catch (err) {
          this.log('error', 'Error cleaning nftables rules on exit:', { error: err.message });
        }
      };
      
      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: NfTableProxyOptions): void {
    // Validate port numbers
    const validatePorts = (port: number | PortRange | Array<number | PortRange>) => {
      if (Array.isArray(port)) {
        port.forEach(p => validatePorts(p));
        return;
      }
      
      if (typeof port === 'number') {
        if (port < 1 || port > 65535) {
          throw new NftValidationError(`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 NftValidationError(`Invalid port range: ${port.from}-${port.to}`);
        }
      }
    };
    
    validatePorts(settings.fromPort);
    validatePorts(settings.toPort);
    
    // Define regex patterns for validation
    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 NftValidationError(`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 NftValidationError(`Invalid host format: ${settings.toHost}`);
      }
    }
    
    // Validate table name to prevent command injection
    if (settings.tableName) {
      const tableNameRegex = /^[a-zA-Z0-9_]+$/;
      if (!tableNameRegex.test(settings.tableName)) {
        throw new NftValidationError(`Invalid table name: ${settings.tableName}. Only alphanumeric characters and underscores are allowed.`);
      }
    }
    
    // Validate QoS settings if enabled
    if (settings.qos?.enabled) {
      if (settings.qos.maxRate) {
        const rateRegex = /^[0-9]+[kKmMgG]?bps$/;
        if (!rateRegex.test(settings.qos.maxRate)) {
          throw new NftValidationError(`Invalid rate format: ${settings.qos.maxRate}. Use format like "10mbps", "1gbps", etc.`);
        }
      }
      
      if (settings.qos.priority !== undefined) {
        if (settings.qos.priority < 1 || settings.qos.priority > 10 || !Number.isInteger(settings.qos.priority)) {
          throw new NftValidationError(`Invalid priority: ${settings.qos.priority}. Must be an integer between 1 and 10.`);
        }
      }
    }
  }
  
  /**
   * Normalizes port specifications into an array of port ranges
   */
  private normalizePortSpec(portSpec: number | PortRange | Array<number | PortRange>): PortRange[] {
    const result: PortRange[] = [];
    
    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;
  }

  /**
   * Execute a command with retry capability
   */
  private async executeWithRetry(command: string, maxRetries = 3, retryDelayMs = 1000): Promise<string> {
    let lastError: Error | undefined;
    
    for (let i = 0; i < maxRetries; i++) {
      try {
        const { stdout } = await execAsync(command);
        return stdout;
      } catch (err) {
        lastError = err;
        this.log('warn', `Command failed (attempt ${i+1}/${maxRetries}): ${command}`, { error: err.message });
        
        // Wait before retry, unless it's the last attempt
        if (i < maxRetries - 1) {
          await new Promise(resolve => setTimeout(resolve, retryDelayMs));
        }
      }
    }
    
    throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
  }

  /**
   * Execute system command synchronously with multiple attempts
   */
  private executeWithRetrySync(command: string, maxRetries = 3, retryDelayMs = 1000): string {
    let lastError: Error | undefined;
    
    for (let i = 0; i < maxRetries; i++) {
      try {
        return execSync(command).toString();
      } catch (err) {
        lastError = err;
        this.log('warn', `Command failed (attempt ${i+1}/${maxRetries}): ${command}`, { error: err.message });
        
        // Wait before retry, unless it's the last attempt
        if (i < maxRetries - 1) {
          // A naive sleep in sync context
          const waitUntil = Date.now() + retryDelayMs;
          while (Date.now() < waitUntil) {
            // busy wait - not great, but this is a fallback method
          }
        }
      }
    }
    
    throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
  }

  /**
   * Checks if nftables is available and the required modules are loaded
   */
  private async checkNftablesAvailability(): Promise<boolean> {
    try {
      await this.executeWithRetry(`${NfTablesProxy.NFT_CMD} --version`, this.settings.maxRetries, this.settings.retryDelayMs);
      
      // Check for conntrack support if we're using advanced NAT
      if (this.settings.useAdvancedNAT) {
        try {
          await this.executeWithRetry('lsmod | grep nf_conntrack', this.settings.maxRetries, this.settings.retryDelayMs);
        } catch (err) {
          this.log('warn', 'Connection tracking modules might not be loaded, advanced NAT features may not work');
        }
      }
      
      return true;
    } catch (err) {
      this.log('error', `nftables is not available: ${err.message}`);
      return false;
    }
  }

  /**
   * Creates the necessary tables and chains
   */
  private async setupTablesAndChains(isIpv6: boolean = false): Promise<boolean> {
    const family = isIpv6 ? 'ip6' : 'ip';
    
    try {
      // Check if the table already exists
      const stdout = await this.executeWithRetry(
        `${NfTablesProxy.NFT_CMD} list tables ${family}`,
        this.settings.maxRetries,
        this.settings.retryDelayMs
      );
      
      const tableExists = stdout.includes(`table ${family} ${this.tableName}`);

      if (!tableExists) {
        // Create the table
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} add table ${family} ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Created table ${family} ${this.tableName}`);
        
        // Create the nat chain for the prerouting hook
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_prerouting { type nat hook prerouting priority -100 ; }`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Created nat_prerouting chain in ${family} ${this.tableName}`);
        
        // Create the nat chain for the postrouting hook if not preserving source IP
        if (!this.settings.preserveSourceIP) {
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_postrouting { type nat hook postrouting priority 100 ; }`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Created nat_postrouting chain in ${family} ${this.tableName}`);
        }
        
        // Create the chain for NetworkProxy integration if needed
        if (this.settings.netProxyIntegration?.enabled && this.settings.netProxyIntegration.redirectLocalhost) {
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_output { type nat hook output priority 0 ; }`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Created nat_output chain in ${family} ${this.tableName}`);
        }
        
        // Create the QoS chain if needed
        if (this.settings.qos?.enabled) {
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} qos_forward { type filter hook forward priority 0 ; }`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Created QoS forward chain in ${family} ${this.tableName}`);
        }
      } else {
        this.log('info', `Table ${family} ${this.tableName} already exists, using existing table`);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to set up tables and chains: ${err.message}`);
      return false;
    }
  }

  /**
   * Creates IP sets for efficient filtering of large IP lists
   */
  private async createIPSet(
    family: string,
    setName: string,
    ips: string[],
    setType: 'ipv4_addr' | 'ipv6_addr' = 'ipv4_addr'
  ): Promise<boolean> {
    try {
      // Filter IPs based on family
      const filteredIPs = ips.filter(ip => {
        if (family === 'ip6' && ip.includes(':')) return true;
        if (family === 'ip' && ip.includes('.')) return true;
        return false;
      });
      
      if (filteredIPs.length === 0) {
        this.log('info', `No IP addresses of type ${setType} to add to set ${setName}`);
        return true;
      }
      
      // Check if set already exists
      try {
        const sets = await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        if (sets.includes(`set ${setName} {`)) {
          this.log('info', `IP set ${setName} already exists, will add elements`);
        } else {
          // Create the set
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Created IP set ${setName} for ${family} with type ${setType}`);
        }
      } catch (err) {
        // Set might not exist yet, create it
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Created IP set ${setName} for ${family} with type ${setType}`);
      }
      
      // Add IPs to the set in batches to avoid command line length limitations
      const batchSize = 100;
      for (let i = 0; i < filteredIPs.length; i += batchSize) {
        const batch = filteredIPs.slice(i, i + batchSize);
        const elements = batch.join(', ');
        
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} add element ${family} ${this.tableName} ${setName} { ${elements} }`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added batch of ${batch.length} IPs to set ${setName}`);
      }
      
      // Track the IP set
      this.ipSets.set(`${family}:${setName}`, filteredIPs);
      
      return true;
    } catch (err) {
      this.log('error', `Failed to create IP set ${setName}: ${err.message}`);
      return false;
    }
  }

  /**
   * Adds source IP filtering rules, potentially using IP sets for efficiency
   */
  private async addSourceIPFilters(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.allowedSourceIPs && !this.settings.bannedSourceIPs) {
      return true; // Nothing to do
    }
    
    const family = isIpv6 ? 'ip6' : 'ip';
    const chain = 'nat_prerouting';
    const setType = isIpv6 ? 'ipv6_addr' : 'ipv4_addr';
    
    try {
      // Start building the ruleset file content
      let rulesetContent = '';
      
      // Using IP sets for more efficient rule processing with large IP lists
      if (this.settings.useIPSets) {
        // Create sets for banned and allowed IPs if needed
        if (this.settings.bannedSourceIPs && this.settings.bannedSourceIPs.length > 0) {
          const setName = 'banned_ips';
          await this.createIPSet(family, setName, this.settings.bannedSourceIPs, setType as any);
          
          // Add rule to drop traffic from banned IPs
          const rule = `add rule ${family} ${this.tableName} ${chain} ip${isIpv6 ? '6' : ''} saddr @${setName} drop comment "${this.ruleTag}:BANNED_SET"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: chain,
            ruleContents: rule,
            added: false
          });
        }
        
        if (this.settings.allowedSourceIPs && this.settings.allowedSourceIPs.length > 0) {
          const setName = 'allowed_ips';
          await this.createIPSet(family, setName, this.settings.allowedSourceIPs, setType as any);
          
          // Add rule to allow traffic from allowed IPs
          const rule = `add rule ${family} ${this.tableName} ${chain} ip${isIpv6 ? '6' : ''} saddr @${setName} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} accept comment "${this.ruleTag}:ALLOWED_SET"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: chain,
            ruleContents: rule,
            added: false
          });
          
          // Add default deny rule for unlisted IPs
          const denyRule = `add rule ${family} ${this.tableName} ${chain} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} drop comment "${this.ruleTag}:DENY_ALL"`;
          rulesetContent += `${denyRule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: chain,
            ruleContents: denyRule,
            added: false
          });
        }
      } else {
        // Traditional approach without IP sets - less efficient for large IP lists
        
        // Ban specific IPs first
        if (this.settings.bannedSourceIPs && this.settings.bannedSourceIPs.length > 0) {
          for (const ip of this.settings.bannedSourceIPs) {
            // Skip IPv4 addresses for IPv6 rules and vice versa
            if (isIpv6 && ip.includes('.')) continue;
            if (!isIpv6 && ip.includes(':')) continue;
            
            const rule = `add rule ${family} ${this.tableName} ${chain} ip${isIpv6 ? '6' : ''} saddr ${ip} drop comment "${this.ruleTag}:BANNED"`;
            rulesetContent += `${rule}\n`;
            
            this.rules.push({
              tableFamily: family,
              tableName: this.tableName,
              chainName: chain,
              ruleContents: rule,
              added: false
            });
          }
        }
        
        // Allow specific IPs
        if (this.settings.allowedSourceIPs && this.settings.allowedSourceIPs.length > 0) {
          // Add rules to allow specific IPs
          for (const ip of this.settings.allowedSourceIPs) {
            // Skip IPv4 addresses for IPv6 rules and vice versa
            if (isIpv6 && ip.includes('.')) continue;
            if (!isIpv6 && ip.includes(':')) continue;
            
            const rule = `add rule ${family} ${this.tableName} ${chain} ip${isIpv6 ? '6' : ''} saddr ${ip} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} accept comment "${this.ruleTag}:ALLOWED"`;
            rulesetContent += `${rule}\n`;
            
            this.rules.push({
              tableFamily: family,
              tableName: this.tableName,
              chainName: chain,
              ruleContents: rule,
              added: false
            });
          }
          
          // Add default deny rule for unlisted IPs
          const denyRule = `add rule ${family} ${this.tableName} ${chain} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} drop comment "${this.ruleTag}:DENY_ALL"`;
          rulesetContent += `${denyRule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: chain,
            ruleContents: denyRule,
            added: false
          });
        }
      }
      
      // Only write and apply if we have rules to add
      if (rulesetContent) {
        // Write the ruleset to a temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added source IP filter rules for ${family}`);
        
        // Mark rules as added
        for (const rule of this.rules) {
          if (rule.tableFamily === family && !rule.added) {
            rule.added = true;
            
            // Verify the rule was applied
            await this.verifyRuleApplication(rule);
          }
        }
        
        // Remove the temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add source IP filter rules: ${err.message}`);
      
      // Try to clean up any rules that might have been added
      this.rollbackRules();
      
      return false;
    }
  }
  
  /**
   * Gets a comma-separated list of all ports from a port specification
   */
  private getAllPorts(portSpec: number | PortRange | Array<number | PortRange>): string {
    const portRanges = this.normalizePortSpec(portSpec);
    const ports: string[] = [];
    
    for (const range of portRanges) {
      if (range.from === range.to) {
        ports.push(range.from.toString());
      } else {
        ports.push(`${range.from}-${range.to}`);
      }
    }
    
    return ports.join(', ');
  }

  /**
   * Configures advanced NAT with connection tracking
   */
  private async setupAdvancedNAT(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.useAdvancedNAT) {
      return true; // Skip if not using advanced NAT
    }
    
    const family = isIpv6 ? 'ip6' : 'ip';
    const preroutingChain = 'nat_prerouting';
    
    try {
      // Get the port ranges
      const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
      const toPortRanges = this.normalizePortSpec(this.settings.toPort);
      
      let rulesetContent = '';
      
      // Simple case - one-to-one mapping with connection tracking
      if (fromPortRanges.length === 1 && toPortRanges.length === 1) {
        const fromRange = fromPortRanges[0];
        const toRange = toPortRanges[0];
        
        // Single port to single port with connection tracking
        if (fromRange.from === fromRange.to && toRange.from === toRange.to) {
          const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${fromRange.from} ct state new dnat to ${this.settings.toHost}:${toRange.from} comment "${this.ruleTag}:DNAT_CT"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: preroutingChain,
            ruleContents: rule,
            added: false
          });
        } 
        // Port range with same size
        else if ((fromRange.to - fromRange.from) === (toRange.to - toRange.from)) {
          const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${fromRange.from}-${fromRange.to} ct state new dnat to ${this.settings.toHost}:${toRange.from}-${toRange.to} comment "${this.ruleTag}:DNAT_RANGE_CT"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: preroutingChain,
            ruleContents: rule,
            added: false
          });
        }
        // Add related and established connection rule for efficient connection handling
        const ctRule = `add rule ${family} ${this.tableName} ${preroutingChain} ct state established,related accept comment "${this.ruleTag}:CT_ESTABLISHED"`;
        rulesetContent += `${ctRule}\n`;
        
        this.rules.push({
          tableFamily: family,
          tableName: this.tableName,
          chainName: preroutingChain,
          ruleContents: ctRule,
          added: false
        });
        
        // Apply the rules if we have any
        if (rulesetContent) {
          fs.writeFileSync(this.tempFilePath, rulesetContent);
          
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Added advanced NAT rules for ${family}`);
          
          // Mark rules as added
          for (const rule of this.rules) {
            if (rule.tableFamily === family && !rule.added) {
              rule.added = true;
              
              // Verify the rule was applied
              await this.verifyRuleApplication(rule);
            }
          }
          
          // Remove the temporary file
          fs.unlinkSync(this.tempFilePath);
        }
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to set up advanced NAT: ${err.message}`);
      return false;
    }
  }

  /**
   * Adds port forwarding rules
   */
  private async addPortForwardingRules(isIpv6: boolean = false): Promise<boolean> {
    // Skip if using advanced NAT as that already handles the port forwarding
    if (this.settings.useAdvancedNAT) {
      return true;
    }
    
    const family = isIpv6 ? 'ip6' : 'ip';
    const preroutingChain = 'nat_prerouting';
    const postroutingChain = 'nat_postrouting';
    
    try {
      // 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
          const singleToRange = toPortRanges[0];
          
          return await this.addPortMappings(family, preroutingChain, postroutingChain, fromPortRanges, singleToRange);
        } else {
          throw new NftValidationError('Mismatched port counts: fromPort and toPort arrays must have equal length or toPort must be a single value');
        }
      } else {
        // Add port mapping rules for each port pair
        return await this.addPortPairMappings(family, preroutingChain, postroutingChain, fromPortRanges, toPortRanges);
      }
    } catch (err) {
      this.log('error', `Failed to add port forwarding rules: ${err.message}`);
      return false;
    }
  }
  
  /**
   * Adds port forwarding rules for the case where one toPortRange maps to multiple fromPortRanges
   */
  private async addPortMappings(
    family: string,
    preroutingChain: string,
    postroutingChain: string,
    fromPortRanges: PortRange[],
    toPortRange: PortRange
  ): Promise<boolean> {
    try {
      let rulesetContent = '';
      
      // For each from port range, create a mapping to the single to port range
      for (const fromRange of fromPortRanges) {
        // Simple case: single port to single port
        if (fromRange.from === fromRange.to && toPortRange.from === toPortRange.to) {
          const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${fromRange.from} dnat to ${this.settings.toHost}:${toPortRange.from} comment "${this.ruleTag}:DNAT"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: preroutingChain,
            ruleContents: rule,
            added: false
          });
        }
        // Multiple ports in from range, but only one port in to range
        else if (toPortRange.from === toPortRange.to) {
          // Map each port in from range to the single to port
          for (let p = fromRange.from; p <= fromRange.to; p++) {
            const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${p} dnat to ${this.settings.toHost}:${toPortRange.from} comment "${this.ruleTag}:DNAT"`;
            rulesetContent += `${rule}\n`;
            
            this.rules.push({
              tableFamily: family,
              tableName: this.tableName,
              chainName: preroutingChain,
              ruleContents: rule,
              added: false
            });
          }
        }
        // Port range to port range mapping with modulo distribution
        else {
          const toRangeSize = toPortRange.to - toPortRange.from + 1;
          
          for (let p = fromRange.from; p <= fromRange.to; p++) {
            const offset = (p - fromRange.from) % toRangeSize;
            const targetPort = toPortRange.from + offset;
            
            const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${p} dnat to ${this.settings.toHost}:${targetPort} comment "${this.ruleTag}:DNAT"`;
            rulesetContent += `${rule}\n`;
            
            this.rules.push({
              tableFamily: family,
              tableName: this.tableName,
              chainName: preroutingChain,
              ruleContents: rule,
              added: false
            });
          }
        }
      }
      
      // Add masquerade rule for source NAT if not preserving source IP
      if (!this.settings.preserveSourceIP) {
        const ports = this.getAllPorts(this.settings.toPort);
        const masqRule = `add rule ${family} ${this.tableName} ${postroutingChain} ${this.settings.protocol} daddr ${this.settings.toHost} dport {${ports}} masquerade comment "${this.ruleTag}:MASQ"`;
        rulesetContent += `${masqRule}\n`;
        
        this.rules.push({
          tableFamily: family,
          tableName: this.tableName,
          chainName: postroutingChain,
          ruleContents: masqRule,
          added: false
        });
      }
      
      // Apply the ruleset if we have any rules
      if (rulesetContent) {
        // Write to temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added port forwarding rules for ${family}`);
        
        // Mark rules as added
        for (const rule of this.rules) {
          if (rule.tableFamily === family && !rule.added) {
            rule.added = true;
            
            // Verify the rule was applied
            await this.verifyRuleApplication(rule);
          }
        }
        
        // Remove temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add port mappings: ${err.message}`);
      return false;
    }
  }
  
  /**
   * Adds port forwarding rules for pairs of fromPortRanges and toPortRanges
   */
  private async addPortPairMappings(
    family: string,
    preroutingChain: string,
    postroutingChain: string,
    fromPortRanges: PortRange[],
    toPortRanges: PortRange[]
  ): Promise<boolean> {
    try {
      let rulesetContent = '';
      
      // Process each fromPort and toPort pair
      for (let i = 0; i < fromPortRanges.length; i++) {
        const fromRange = fromPortRanges[i];
        const toRange = toPortRanges[i];
        
        // Simple case: single port to single port
        if (fromRange.from === fromRange.to && toRange.from === toRange.to) {
          const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${fromRange.from} dnat to ${this.settings.toHost}:${toRange.from} comment "${this.ruleTag}:DNAT"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: preroutingChain,
            ruleContents: rule,
            added: false
          });
        }
        // Port range with equal size - can use direct mapping
        else if ((fromRange.to - fromRange.from) === (toRange.to - toRange.from)) {
          const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${fromRange.from}-${fromRange.to} dnat to ${this.settings.toHost}:${toRange.from}-${toRange.to} comment "${this.ruleTag}:DNAT_RANGE"`;
          rulesetContent += `${rule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: preroutingChain,
            ruleContents: rule,
            added: false
          });
        }
        // Unequal port ranges - need to map individually
        else {
          const toRangeSize = toRange.to - toRange.from + 1;
          
          for (let p = fromRange.from; p <= fromRange.to; p++) {
            const offset = (p - fromRange.from) % toRangeSize;
            const targetPort = toRange.from + offset;
            
            const rule = `add rule ${family} ${this.tableName} ${preroutingChain} ${this.settings.protocol} dport ${p} dnat to ${this.settings.toHost}:${targetPort} comment "${this.ruleTag}:DNAT_INDIVIDUAL"`;
            rulesetContent += `${rule}\n`;
            
            this.rules.push({
              tableFamily: family,
              tableName: this.tableName,
              chainName: preroutingChain,
              ruleContents: rule,
              added: false
            });
          }
        }
        
        // Add masquerade rule for this port range if not preserving source IP
        if (!this.settings.preserveSourceIP) {
          const masqRule = `add rule ${family} ${this.tableName} ${postroutingChain} ${this.settings.protocol} daddr ${this.settings.toHost} dport ${toRange.from}-${toRange.to} masquerade comment "${this.ruleTag}:MASQ"`;
          rulesetContent += `${masqRule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: postroutingChain,
            ruleContents: masqRule,
            added: false
          });
        }
      }
      
      // Apply the ruleset if we have any rules
      if (rulesetContent) {
        // Write to temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added port forwarding rules for ${family}`);
        
        // Mark rules as added
        for (const rule of this.rules) {
          if (rule.tableFamily === family && !rule.added) {
            rule.added = true;
            
            // Verify the rule was applied
            await this.verifyRuleApplication(rule);
          }
        }
        
        // Remove temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add port pair mappings: ${err.message}`);
      return false;
    }
  }

  /**
   * Setup quality of service rules
   */
  private async addTrafficShaping(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.qos?.enabled) {
      return true;
    }
    
    const family = isIpv6 ? 'ip6' : 'ip';
    const qosChain = 'qos_forward';
    
    try {
      let rulesetContent = '';
      
      // Add rate limiting rule if specified
      if (this.settings.qos.maxRate) {
        const ruleContent = `add rule ${family} ${this.tableName} ${qosChain} ip daddr ${this.settings.toHost} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.toPort)}} limit rate over ${this.settings.qos.maxRate} drop comment "${this.ruleTag}:QOS_RATE"`;
        rulesetContent += `${ruleContent}\n`;
        
        this.rules.push({
          tableFamily: family,
          tableName: this.tableName,
          chainName: qosChain,
          ruleContents: ruleContent,
          added: false
        });
      }
      
      // Add priority marking if specified
      if (this.settings.qos.priority !== undefined) {
        // Check if the chain exists
        const chainsOutput = await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} list chains ${family} ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        // Check if we need to create priority queues
        const hasPrioChain = chainsOutput.includes(`chain prio${this.settings.qos.priority}`);
        
        if (!hasPrioChain) {
          // Create priority chain
          const prioChainRule = `add chain ${family} ${this.tableName} prio${this.settings.qos.priority} { type filter hook forward priority ${this.settings.qos.priority * 10}; }`;
          rulesetContent += `${prioChainRule}\n`;
        }
        
        // Add the rules to mark packets with this priority
        for (const range of this.normalizePortSpec(this.settings.toPort)) {
          const markRule = `add rule ${family} ${this.tableName} ${qosChain} ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`;
          rulesetContent += `${markRule}\n`;
          
          this.rules.push({
            tableFamily: family,
            tableName: this.tableName,
            chainName: qosChain,
            ruleContents: markRule,
            added: false
          });
        }
      }
      
      // Apply the ruleset if we have any rules
      if (rulesetContent) {
        // Write to temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added QoS rules for ${family}`);
        
        // Mark rules as added
        for (const rule of this.rules) {
          if (rule.tableFamily === family && !rule.added) {
            rule.added = true;
            
            // Verify the rule was applied
            await this.verifyRuleApplication(rule);
          }
        }
        
        // Remove temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add traffic shaping: ${err.message}`);
      return false;
    }
  }

  /**
   * Setup NetworkProxy integration rules
   */
  private async setupNetworkProxyIntegration(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.netProxyIntegration?.enabled) {
      return true;
    }
    
    const netProxyConfig = this.settings.netProxyIntegration;
    const family = isIpv6 ? 'ip6' : 'ip';
    const outputChain = 'nat_output';
    
    try {
      // Only proceed if we're redirecting localhost and have a port
      if (netProxyConfig.redirectLocalhost && netProxyConfig.sslTerminationPort) {
        const localhost = isIpv6 ? '::1' : '127.0.0.1';
        
        // Create the redirect rule
        const rule = `add rule ${family} ${this.tableName} ${outputChain} ${this.settings.protocol} daddr ${localhost} redirect to :${netProxyConfig.sslTerminationPort} comment "${this.ruleTag}:NETPROXY_REDIRECT"`;
        
        // Apply the rule
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} ${rule}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', `Added NetworkProxy redirection rule for ${family}`);
        
        const newRule = {
          tableFamily: family,
          tableName: this.tableName,
          chainName: outputChain,
          ruleContents: rule,
          added: true
        };
        
        this.rules.push(newRule);
        
        // Verify the rule was actually applied
        await this.verifyRuleApplication(newRule);
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to set up NetworkProxy integration: ${err.message}`);
      return false;
    }
  }

  /**
   * Verify that a rule was successfully applied
   */
  private async verifyRuleApplication(rule: NfTablesRule): Promise<boolean> {
    try {
      const { tableFamily, tableName, chainName, ruleContents } = rule;
      
      // Extract the distinctive parts of the rule to create a search pattern
      const commentMatch = ruleContents.match(/comment "([^"]+)"/);
      if (!commentMatch) return false;
      
      const commentTag = commentMatch[1];
      
      // List the chain to check if our rule is there
      const stdout = await this.executeWithRetry(
        `${NfTablesProxy.NFT_CMD} list chain ${tableFamily} ${tableName} ${chainName}`,
        this.settings.maxRetries,
        this.settings.retryDelayMs
      );
      
      // Check if the comment appears in the output
      const isApplied = stdout.includes(commentTag);
      
      rule.verified = isApplied;
      
      if (!isApplied) {
        this.log('warn', `Rule verification failed: ${commentTag} not found in chain ${chainName}`);
      } else {
        this.log('debug', `Rule verified: ${commentTag} found in chain ${chainName}`);
      }
      
      return isApplied;
    } catch (err) {
      this.log('error', `Failed to verify rule application: ${err.message}`);
      return false;
    }
  }

  /**
   * Rolls back rules in case of error during setup
   */
  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 {
          // For nftables, create a delete rule by replacing 'add' with 'delete'
          const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} ${deleteRule}`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Rolled back rule: ${deleteRule}`);
          
          rule.added = false;
          rule.verified = false;
        } catch (err) {
          this.log('error', `Failed to roll back rule: ${err.message}`);
        }
      }
    }
  }

  /**
   * Checks if nftables table exists
   */
  private async tableExists(family: string, tableName: string): Promise<boolean> {
    try {
      const stdout = await this.executeWithRetry(
        `${NfTablesProxy.NFT_CMD} list tables ${family}`,
        this.settings.maxRetries,
        this.settings.retryDelayMs
      );
      
      return stdout.includes(`table ${family} ${tableName}`);
    } catch (err) {
      return false;
    }
  }

  /**
   * Get system metrics like connection counts
   */
  private async getSystemMetrics(): Promise<{
    activeConnections?: number;
    forwardedConnections?: number;
    bytesForwarded?: { sent: number; received: number };
  }> {
    const metrics: {
      activeConnections?: number;
      forwardedConnections?: number;
      bytesForwarded?: { sent: number; received: number };
    } = {};
    
    try {
      // Try to get connection metrics if conntrack is available
      try {
        const stdout = await this.executeWithRetry('conntrack -C', this.settings.maxRetries, this.settings.retryDelayMs);
        metrics.activeConnections = parseInt(stdout.trim(), 10);
      } catch (err) {
        // conntrack not available, skip this metric
      }
      
      // Try to get forwarded connections count from nftables counters
      try {
        // Look for counters in our rules
        const stdout = await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} list table ip ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        // Parse counter information from the output
        const counterMatches = stdout.matchAll(/counter packets (\d+) bytes (\d+)/g);
        let totalPackets = 0;
        let totalBytes = 0;
        
        for (const match of counterMatches) {
          totalPackets += parseInt(match[1], 10);
          totalBytes += parseInt(match[2], 10);
        }
        
        if (totalPackets > 0) {
          metrics.forwardedConnections = totalPackets;
          metrics.bytesForwarded = {
            sent: totalBytes,
            received: 0  // We can't easily determine this without additional rules
          };
        }
      } catch (err) {
        // Failed to get counter info, skip this metric
      }
      
      return metrics;
    } catch (err) {
      this.log('error', `Failed to get system metrics: ${err.message}`);
      return metrics;
    }
  }

  /**
   * Get status of IP sets
   */
  private async getIPSetStatus(): Promise<{
    name: string;
    elementCount: number;
    type: string;
  }[]> {
    const result: {
      name: string;
      elementCount: number;
      type: string;
    }[] = [];
    
    try {
      for (const family of ['ip', 'ip6']) {
        try {
          const stdout = await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          const setMatches = stdout.matchAll(/set (\w+) {\s*type (\w+)/g);
          
          for (const match of setMatches) {
            const setName = match[1];
            const setType = match[2];
            
            // Get element count from tracking map
            const setKey = `${family}:${setName}`;
            const elements = this.ipSets.get(setKey) || [];
            
            result.push({
              name: setName,
              elementCount: elements.length,
              type: setType
            });
          }
        } catch (err) {
          // No sets for this family, or table doesn't exist
        }
      }
      
      return result;
    } catch (err) {
      this.log('error', `Failed to get IP set status: ${err.message}`);
      return result;
    }
  }

  /**
   * Get detailed status about the current state of the proxy
   */
  public async getStatus(): Promise<NfTablesStatus> {
    const result: NfTablesStatus = {
      active: this.rules.some(r => r.added),
      ruleCount: {
        total: this.rules.length,
        added: this.rules.filter(r => r.added).length,
        verified: this.rules.filter(r => r.verified).length
      },
      tablesConfigured: [],
      metrics: {},
      qosEnabled: this.settings.qos?.enabled || false
    };
    
    try {
      // Get list of configured tables
      const stdout = await this.executeWithRetry(
        `${NfTablesProxy.NFT_CMD} list tables`,
        this.settings.maxRetries,
        this.settings.retryDelayMs
      );
      
      const tableRegex = /table (ip|ip6) (\w+)/g;
      let match;
      
      while ((match = tableRegex.exec(stdout)) !== null) {
        const [, family, name] = match;
        if (name === this.tableName) {
          result.tablesConfigured.push({ family, tableName: name });
        }
      }
      
      // Get system metrics
      result.metrics = await this.getSystemMetrics();
      
      // Get IP set status if using IP sets
      if (this.settings.useIPSets) {
        result.ipSetsConfigured = await this.getIPSetStatus();
      }
      
      return result;
    } catch (err) {
      this.log('error', `Failed to get status: ${err.message}`);
      return result;
    }
  }

  /**
   * Performs a dry run to see what commands would be executed without actually applying them
   */
  public async dryRun(): Promise<string[]> {
    const commands: string[] = [];
    
    // Simulate all the necessary setup steps and collect commands
    
    // Tables and chains
    commands.push(`add table ip ${this.tableName}`);
    commands.push(`add chain ip ${this.tableName} nat_prerouting { type nat hook prerouting priority -100; }`);
    
    if (!this.settings.preserveSourceIP) {
      commands.push(`add chain ip ${this.tableName} nat_postrouting { type nat hook postrouting priority 100; }`);
    }
    
    if (this.settings.netProxyIntegration?.enabled && this.settings.netProxyIntegration.redirectLocalhost) {
      commands.push(`add chain ip ${this.tableName} nat_output { type nat hook output priority 0; }`);
    }
    
    if (this.settings.qos?.enabled) {
      commands.push(`add chain ip ${this.tableName} qos_forward { type filter hook forward priority 0; }`);
    }
    
    // Add IPv6 tables if enabled
    if (this.settings.ipv6Support) {
      commands.push(`add table ip6 ${this.tableName}`);
      commands.push(`add chain ip6 ${this.tableName} nat_prerouting { type nat hook prerouting priority -100; }`);
      
      if (!this.settings.preserveSourceIP) {
        commands.push(`add chain ip6 ${this.tableName} nat_postrouting { type nat hook postrouting priority 100; }`);
      }
      
      if (this.settings.netProxyIntegration?.enabled && this.settings.netProxyIntegration.redirectLocalhost) {
        commands.push(`add chain ip6 ${this.tableName} nat_output { type nat hook output priority 0; }`);
      }
      
      if (this.settings.qos?.enabled) {
        commands.push(`add chain ip6 ${this.tableName} qos_forward { type filter hook forward priority 0; }`);
      }
    }
    
    // Source IP filters
    if (this.settings.useIPSets) {
      if (this.settings.bannedSourceIPs?.length) {
        commands.push(`add set ip ${this.tableName} banned_ips { type ipv4_addr; }`);
        commands.push(`add element ip ${this.tableName} banned_ips { ${this.settings.bannedSourceIPs.join(', ')} }`);
        commands.push(`add rule ip ${this.tableName} nat_prerouting ip saddr @banned_ips drop comment "${this.ruleTag}:BANNED_SET"`);
      }
      
      if (this.settings.allowedSourceIPs?.length) {
        commands.push(`add set ip ${this.tableName} allowed_ips { type ipv4_addr; }`);
        commands.push(`add element ip ${this.tableName} allowed_ips { ${this.settings.allowedSourceIPs.join(', ')} }`);
        commands.push(`add rule ip ${this.tableName} nat_prerouting ip saddr @allowed_ips ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} accept comment "${this.ruleTag}:ALLOWED_SET"`);
        commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} drop comment "${this.ruleTag}:DENY_ALL"`);
      }
    } else if (this.settings.bannedSourceIPs?.length || this.settings.allowedSourceIPs?.length) {
      // Traditional approach without IP sets
      if (this.settings.bannedSourceIPs?.length) {
        for (const ip of this.settings.bannedSourceIPs) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ip saddr ${ip} drop comment "${this.ruleTag}:BANNED"`);
        }
      }
      
      if (this.settings.allowedSourceIPs?.length) {
        for (const ip of this.settings.allowedSourceIPs) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ip saddr ${ip} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} accept comment "${this.ruleTag}:ALLOWED"`);
        }
        commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport {${this.getAllPorts(this.settings.fromPort)}} drop comment "${this.ruleTag}:DENY_ALL"`);
      }
    }
    
    // Port forwarding rules
    if (this.settings.useAdvancedNAT) {
      // Advanced NAT with connection tracking
      const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
      const toPortRanges = this.normalizePortSpec(this.settings.toPort);
      
      if (fromPortRanges.length === 1 && toPortRanges.length === 1) {
        const fromRange = fromPortRanges[0];
        const toRange = toPortRanges[0];
        
        if (fromRange.from === fromRange.to && toRange.from === toRange.to) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRange.from} ct state new dnat to ${this.settings.toHost}:${toRange.from} comment "${this.ruleTag}:DNAT_CT"`);
        } else if ((fromRange.to - fromRange.from) === (toRange.to - toRange.from)) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRange.from}-${fromRange.to} ct state new dnat to ${this.settings.toHost}:${toRange.from}-${toRange.to} comment "${this.ruleTag}:DNAT_RANGE_CT"`);
        }
        
        commands.push(`add rule ip ${this.tableName} nat_prerouting ct state established,related accept comment "${this.ruleTag}:CT_ESTABLISHED"`);
      }
    } else {
      // Standard NAT rules
      const fromRanges = this.normalizePortSpec(this.settings.fromPort);
      const toRanges = this.normalizePortSpec(this.settings.toPort);
      
      if (fromRanges.length === 1 && toRanges.length === 1) {
        const fromRange = fromRanges[0];
        const toRange = toRanges[0];
        
        if (fromRange.from === fromRange.to && toRange.from === toRange.to) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRange.from} dnat to ${this.settings.toHost}:${toRange.from} comment "${this.ruleTag}:DNAT"`);
        } else {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRange.from}-${fromRange.to} dnat to ${this.settings.toHost}:${toRange.from}-${toRange.to} comment "${this.ruleTag}:DNAT_RANGE"`);
        }
      } else if (toRanges.length === 1) {
        // One-to-many mapping
        for (const fromRange of fromRanges) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRange.from}-${fromRange.to} dnat to ${this.settings.toHost}:${toRanges[0].from}-${toRanges[0].to} comment "${this.ruleTag}:DNAT_RANGE"`);
        }
      } else {
        // One-to-one mapping of multiple ranges
        for (let i = 0; i < fromRanges.length; i++) {
          commands.push(`add rule ip ${this.tableName} nat_prerouting ${this.settings.protocol} dport ${fromRanges[i].from}-${fromRanges[i].to} dnat to ${this.settings.toHost}:${toRanges[i].from}-${toRanges[i].to} comment "${this.ruleTag}:DNAT_RANGE"`);
        }
      }
    }
    
    // Masquerade rules if not preserving source IP
    if (!this.settings.preserveSourceIP) {
      commands.push(`add rule ip ${this.tableName} nat_postrouting ${this.settings.protocol} daddr ${this.settings.toHost} dport {${this.getAllPorts(this.settings.toPort)}} masquerade comment "${this.ruleTag}:MASQ"`);
    }
    
    // NetworkProxy integration
    if (this.settings.netProxyIntegration?.enabled && 
        this.settings.netProxyIntegration.redirectLocalhost && 
        this.settings.netProxyIntegration.sslTerminationPort) {
      
      commands.push(`add rule ip ${this.tableName} nat_output ${this.settings.protocol} daddr 127.0.0.1 redirect to :${this.settings.netProxyIntegration.sslTerminationPort} comment "${this.ruleTag}:NETPROXY_REDIRECT"`);
    }
    
    // QoS rules
    if (this.settings.qos?.enabled) {
      if (this.settings.qos.maxRate) {
        commands.push(`add rule ip ${this.tableName} qos_forward ip daddr ${this.settings.toHost} ${this.settings.protocol} dport {${this.getAllPorts(this.settings.toPort)}} limit rate over ${this.settings.qos.maxRate} drop comment "${this.ruleTag}:QOS_RATE"`);
      }
      
      if (this.settings.qos.priority !== undefined) {
        commands.push(`add chain ip ${this.tableName} prio${this.settings.qos.priority} { type filter hook forward priority ${this.settings.qos.priority * 10}; }`);
        
        for (const range of this.normalizePortSpec(this.settings.toPort)) {
          commands.push(`add rule ip ${this.tableName} qos_forward ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`);
        }
      }
    }
    
    return commands;
  }

  /**
   * Starts the proxy by setting up all nftables rules
   */
  public async start(): Promise<void> {
    // Check if nftables is available
    const nftablesAvailable = await this.checkNftablesAvailability();
    if (!nftablesAvailable) {
      throw new NftResourceError('nftables is not available or not properly configured');
    }
    
    // Optionally clean slate first
    if (this.settings.forceCleanSlate) {
      await NfTablesProxy.cleanSlate();
    }
    
    // Set up tables and chains for IPv4
    const setupSuccess = await this.setupTablesAndChains();
    if (!setupSuccess) {
      throw new NftExecutionError('Failed to set up nftables tables and chains');
    }
    
    // Set up IPv6 tables and chains if enabled
    if (this.settings.ipv6Support) {
      const setupIPv6Success = await this.setupTablesAndChains(true);
      if (!setupIPv6Success) {
        this.log('warn', 'Failed to set up IPv6 tables and chains, continuing with IPv4 only');
      }
    }
    
    // Add source IP filters
    await this.addSourceIPFilters();
    if (this.settings.ipv6Support) {
      await this.addSourceIPFilters(true);
    }
    
    // Set up advanced NAT with connection tracking if enabled
    if (this.settings.useAdvancedNAT) {
      const advancedNatSuccess = await this.setupAdvancedNAT();
      if (!advancedNatSuccess) {
        this.log('warn', 'Failed to set up advanced NAT, falling back to standard NAT');
        this.settings.useAdvancedNAT = false;
      } else if (this.settings.ipv6Support) {
        await this.setupAdvancedNAT(true);
      }
    }
    
    // Add port forwarding rules (skip if using advanced NAT)
    if (!this.settings.useAdvancedNAT) {
      const forwardingSuccess = await this.addPortForwardingRules();
      if (!forwardingSuccess) {
        throw new NftExecutionError('Failed to add port forwarding rules');
      }
      
      // Add IPv6 port forwarding rules if enabled
      if (this.settings.ipv6Support) {
        const forwardingIPv6Success = await this.addPortForwardingRules(true);
        if (!forwardingIPv6Success) {
          this.log('warn', 'Failed to add IPv6 port forwarding rules');
        }
      }
    }
    
    // Set up QoS if enabled
    if (this.settings.qos?.enabled) {
      const qosSuccess = await this.addTrafficShaping();
      if (!qosSuccess) {
        this.log('warn', 'Failed to set up QoS rules, continuing without traffic shaping');
      } else if (this.settings.ipv6Support) {
        await this.addTrafficShaping(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);
      }
    }
    
    // Final check - ensure we have at least one rule added
    if (this.rules.filter(r => r.added).length === 0) {
      throw new NftExecutionError('No rules were added');
    }
    
    this.log('info', 'NfTablesProxy started successfully');
  }

  /**
   * Stops the proxy by removing all added rules
   */
  public async stop(): Promise<void> {
    try {
      let rulesetContent = '';
      
      // Process rules in reverse order (LIFO)
      for (let i = this.rules.length - 1; i >= 0; i--) {
        const rule = this.rules[i];
        
        if (rule.added) {
          // Create delete rules by replacing 'add' with 'delete'
          const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
          rulesetContent += `${deleteRule}\n`;
        }
      }
      
      // Apply the ruleset if we have any rules to delete
      if (rulesetContent) {
        // Write to temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', 'Removed all added rules');
        
        // Mark all rules as removed
        this.rules.forEach(rule => {
          rule.added = false;
          rule.verified = false;
        });
        
        // Remove temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      // Clean up IP sets if we created any
      if (this.settings.useIPSets && this.ipSets.size > 0) {
        for (const [key, _] of this.ipSets) {
          const [family, setName] = key.split(':');
          
          try {
            await this.executeWithRetry(
              `${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`,
              this.settings.maxRetries,
              this.settings.retryDelayMs
            );
            
            this.log('info', `Removed IP set ${setName} from ${family} ${this.tableName}`);
          } catch (err) {
            this.log('warn', `Failed to remove IP set ${setName}: ${err.message}`);
          }
        }
        
        this.ipSets.clear();
      }
      
      // Optionally clean up tables if they're empty
      await this.cleanupEmptyTables();
      
      this.log('info', 'NfTablesProxy stopped successfully');
    } catch (err) {
      this.log('error', `Error stopping NfTablesProxy: ${err.message}`);
      throw err;
    }
  }

  /**
   * Synchronous version of stop, for use in exit handlers
   */
  public stopSync(): void {
    try {
      let rulesetContent = '';
      
      // Process rules in reverse order (LIFO)
      for (let i = this.rules.length - 1; i >= 0; i--) {
        const rule = this.rules[i];
        
        if (rule.added) {
          // Create delete rules by replacing 'add' with 'delete'
          const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
          rulesetContent += `${deleteRule}\n`;
        }
      }
      
      // Apply the ruleset if we have any rules to delete
      if (rulesetContent) {
        // Write to temporary file
        fs.writeFileSync(this.tempFilePath, rulesetContent);
        
        // Apply the ruleset
        this.executeWithRetrySync(
          `${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        this.log('info', 'Removed all added rules');
        
        // Mark all rules as removed
        this.rules.forEach(rule => {
          rule.added = false;
          rule.verified = false;
        });
        
        // Remove temporary file
        fs.unlinkSync(this.tempFilePath);
      }
      
      // Clean up IP sets if we created any
      if (this.settings.useIPSets && this.ipSets.size > 0) {
        for (const [key, _] of this.ipSets) {
          const [family, setName] = key.split(':');
          
          try {
            this.executeWithRetrySync(
              `${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`,
              this.settings.maxRetries,
              this.settings.retryDelayMs
            );
          } catch (err) {
            // Non-critical error, continue
          }
        }
      }
      
      // Optionally clean up tables if they're empty (sync version)
      this.cleanupEmptyTablesSync();
      
      this.log('info', 'NfTablesProxy stopped successfully');
    } catch (err) {
      this.log('error', `Error stopping NfTablesProxy: ${err.message}`);
    }
  }

  /**
   * Cleans up empty tables
   */
  private async cleanupEmptyTables(): Promise<void> {
    // Check if tables are empty, and if so, delete them
    for (const family of ['ip', 'ip6']) {
      // Skip IPv6 if not enabled
      if (family === 'ip6' && !this.settings.ipv6Support) {
        continue;
      }
      
      try {
        // Check if table exists
        const tableExists = await this.tableExists(family, this.tableName);
        if (!tableExists) {
          continue;
        }
        
        // Check if the table has any rules
        const stdout = await this.executeWithRetry(
          `${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        const hasRules = stdout.includes('rule');
        
        if (!hasRules) {
          // Table is empty, delete it
          await this.executeWithRetry(
            `${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Deleted empty table ${family} ${this.tableName}`);
        }
      } catch (err) {
        this.log('error', `Error cleaning up tables: ${err.message}`);
      }
    }
  }

  /**
   * Synchronous version of cleanupEmptyTables
   */
  private cleanupEmptyTablesSync(): void {
    // Check if tables are empty, and if so, delete them
    for (const family of ['ip', 'ip6']) {
      // Skip IPv6 if not enabled
      if (family === 'ip6' && !this.settings.ipv6Support) {
        continue;
      }
      
      try {
        // Check if table exists
        const tableExistsOutput = this.executeWithRetrySync(
          `${NfTablesProxy.NFT_CMD} list tables ${family}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        const tableExists = tableExistsOutput.includes(`table ${family} ${this.tableName}`);
        
        if (!tableExists) {
          continue;
        }
        
        // Check if the table has any rules
        const stdout = this.executeWithRetrySync(
          `${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`,
          this.settings.maxRetries,
          this.settings.retryDelayMs
        );
        
        const hasRules = stdout.includes('rule');
        
        if (!hasRules) {
          // Table is empty, delete it
          this.executeWithRetrySync(
            `${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`,
            this.settings.maxRetries,
            this.settings.retryDelayMs
          );
          
          this.log('info', `Deleted empty table ${family} ${this.tableName}`);
        }
      } catch (err) {
        this.log('error', `Error cleaning up tables: ${err.message}`);
      }
    }
  }

  /**
   * Removes all nftables rules created by this module
   */
  public static async cleanSlate(): Promise<void> {
    try {
      // Check for rules with our comment pattern
      const stdout = await execAsync(`${NfTablesProxy.NFT_CMD} list ruleset`);
      
      // Extract our tables
      const tableMatches = stdout.stdout.match(/table (ip|ip6) (\w+) {[^}]*NfTablesProxy:[^}]*}/g);
      
      if (tableMatches) {
        for (const tableMatch of tableMatches) {
          // Extract table family and name
          const familyMatch = tableMatch.match(/table (ip|ip6) (\w+)/);
          if (familyMatch) {
            const family = familyMatch[1];
            const tableName = familyMatch[2];
            
            // Delete the table
            await execAsync(`${NfTablesProxy.NFT_CMD} delete table ${family} ${tableName}`);
            console.log(`Deleted table ${family} ${tableName} containing NfTablesProxy rules`);
          }
        }
      } else {
        console.log('No NfTablesProxy rules found to clean up');
      }
    } catch (err) {
      console.error(`Error in cleanSlate: ${err}`);
    }
  }

  /**
   * Synchronous version of cleanSlate
   */
  public static cleanSlateSync(): void {
    try {
      // Check for rules with our comment pattern
      const stdout = execSync(`${NfTablesProxy.NFT_CMD} list ruleset`).toString();
      
      // Extract our tables
      const tableMatches = stdout.match(/table (ip|ip6) (\w+) {[^}]*NfTablesProxy:[^}]*}/g);
      
      if (tableMatches) {
        for (const tableMatch of tableMatches) {
          // Extract table family and name
          const familyMatch = tableMatch.match(/table (ip|ip6) (\w+)/);
          if (familyMatch) {
            const family = familyMatch[1];
            const tableName = familyMatch[2];
            
            // Delete the table
            execSync(`${NfTablesProxy.NFT_CMD} delete table ${family} ${tableName}`);
            console.log(`Deleted table ${family} ${tableName} containing NfTablesProxy rules`);
          }
        }
      } else {
        console.log('No NfTablesProxy rules found to clean up');
      }
    } catch (err) {
      console.error(`Error in cleanSlateSync: ${err}`);
    }
  }
  
  /**
   * Improved logging with structured output
   */
  private log(level: 'info' | 'warn' | 'error' | 'debug', message: string, meta?: Record<string, any>): void {
    if (!this.settings.enableLogging && (level === 'info' || level === 'debug')) {
      return;
    }
    
    const timestamp = new Date().toISOString();
    
    const logData = {
      timestamp,
      level: level.toUpperCase(),
      message,
      ...meta,
      context: {
        instance: this.ruleTag,
        table: this.tableName
      }
    };
    
    // Determine if output should be JSON or plain text based on settings
    const useJson = this.settings.logFormat === 'json';
    
    if (useJson) {
      const logOutput = JSON.stringify(logData);
      console.log(logOutput);
      return;
    }
    
    // Plain text format
    const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
    
    switch (level) {
      case 'info':
        console.log(`[${timestamp}] [INFO] ${message}${metaStr}`);
        break;
      case 'warn':
        console.warn(`[${timestamp}] [WARN] ${message}${metaStr}`);
        break;
      case 'error':
        console.error(`[${timestamp}] [ERROR] ${message}${metaStr}`);
        break;
      case 'debug':
        console.log(`[${timestamp}] [DEBUG] ${message}${metaStr}`);
        break;
    }
  }
}