import type{ IPortProxySettings } from './classes.pp.interfaces.js';

/**
 * Manages port ranges and port-based configuration
 */
export class PortRangeManager {
  constructor(private settings: IPortProxySettings) {}
  
  /**
   * Get all ports that should be listened on
   */
  public getListeningPorts(): Set<number> {
    const listeningPorts = new Set<number>();
    
    // Always include the main fromPort
    listeningPorts.add(this.settings.fromPort);
    
    // Add ports from global port ranges if defined
    if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) {
      for (const range of this.settings.globalPortRanges) {
        for (let port = range.from; port <= range.to; port++) {
          listeningPorts.add(port);
        }
      }
    }
    
    return listeningPorts;
  }
  
  /**
   * Check if a port should use NetworkProxy for forwarding
   */
  public shouldUseNetworkProxy(port: number): boolean {
    return !!this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(port);
  }
  
  /**
   * Check if port should use global forwarding
   */
  public shouldUseGlobalForwarding(port: number): boolean {
    return (
      !!this.settings.forwardAllGlobalRanges &&
      this.isPortInGlobalRanges(port)
    );
  }
  
  /**
   * Check if a port is in global ranges
   */
  public isPortInGlobalRanges(port: number): boolean {
    return (
      this.settings.globalPortRanges &&
      this.isPortInRanges(port, this.settings.globalPortRanges)
    );
  }
  
  /**
   * Check if a port falls within the specified ranges
   */
  public isPortInRanges(port: number, ranges: Array<{ from: number; to: number }>): boolean {
    return ranges.some((range) => port >= range.from && port <= range.to);
  }
  
  /**
   * Get forwarding port for a specific listening port
   * This determines what port to connect to on the target
   */
  public getForwardingPort(listeningPort: number): number {
    // If using global forwarding, forward to the original port
    if (this.settings.forwardAllGlobalRanges && this.isPortInGlobalRanges(listeningPort)) {
      return listeningPort;
    }
    
    // Otherwise use the configured toPort
    return this.settings.toPort;
  }
  
  /**
   * Find domain-specific port ranges that include a given port
   */
  public findDomainPortRange(port: number): { 
    domainIndex: number, 
    range: { from: number, to: number } 
  } | undefined {
    for (let i = 0; i < this.settings.domainConfigs.length; i++) {
      const domain = this.settings.domainConfigs[i];
      if (domain.portRanges) {
        for (const range of domain.portRanges) {
          if (port >= range.from && port <= range.to) {
            return { domainIndex: i, range };
          }
        }
      }
    }
    return undefined;
  }
  
  /**
   * Get a list of all configured ports
   * This includes the fromPort, NetworkProxy ports, and ports from all ranges
   */
  public getAllConfiguredPorts(): number[] {
    const ports = new Set<number>();
    
    // Add main listening port
    ports.add(this.settings.fromPort);
    
    // Add NetworkProxy port if configured
    if (this.settings.networkProxyPort) {
      ports.add(this.settings.networkProxyPort);
    }
    
    // Add NetworkProxy ports
    if (this.settings.useNetworkProxy) {
      for (const port of this.settings.useNetworkProxy) {
        ports.add(port);
      }
    }
    
    // Add ACME HTTP challenge port if enabled
    if (this.settings.acme?.enabled && this.settings.acme.port) {
      ports.add(this.settings.acme.port);
    }
    
    // Add global port ranges
    if (this.settings.globalPortRanges) {
      for (const range of this.settings.globalPortRanges) {
        for (let port = range.from; port <= range.to; port++) {
          ports.add(port);
        }
      }
    }
    
    // Add domain-specific port ranges
    for (const domain of this.settings.domainConfigs) {
      if (domain.portRanges) {
        for (const range of domain.portRanges) {
          for (let port = range.from; port <= range.to; port++) {
            ports.add(port);
          }
        }
      }
      
      // Add domain-specific NetworkProxy port if configured
      if (domain.useNetworkProxy && domain.networkProxyPort) {
        ports.add(domain.networkProxyPort);
      }
    }
    
    return Array.from(ports);
  }
  
  /**
   * Validate port configuration
   * Returns array of warning messages
   */
  public validateConfiguration(): string[] {
    const warnings: string[] = [];
    
    // Check for overlapping port ranges
    const portMappings = new Map<number, string[]>();
    
    // Track global port ranges
    if (this.settings.globalPortRanges) {
      for (const range of this.settings.globalPortRanges) {
        for (let port = range.from; port <= range.to; port++) {
          if (!portMappings.has(port)) {
            portMappings.set(port, []);
          }
          portMappings.get(port)!.push('Global Port Range');
        }
      }
    }
    
    // Track domain-specific port ranges
    for (const domain of this.settings.domainConfigs) {
      if (domain.portRanges) {
        for (const range of domain.portRanges) {
          for (let port = range.from; port <= range.to; port++) {
            if (!portMappings.has(port)) {
              portMappings.set(port, []);
            }
            portMappings.get(port)!.push(`Domain: ${domain.domains.join(', ')}`);
          }
        }
      }
    }
    
    // Check for ports with multiple mappings
    for (const [port, mappings] of portMappings.entries()) {
      if (mappings.length > 1) {
        warnings.push(`Port ${port} has multiple mappings: ${mappings.join(', ')}`);
      }
    }
    
    // Check if main ports are used elsewhere
    if (portMappings.has(this.settings.fromPort) && portMappings.get(this.settings.fromPort)!.length > 0) {
      warnings.push(`Main listening port ${this.settings.fromPort} is also used in port ranges`);
    }
    
    if (this.settings.networkProxyPort && portMappings.has(this.settings.networkProxyPort)) {
      warnings.push(`NetworkProxy port ${this.settings.networkProxyPort} is also used in port ranges`);
    }
    
    // Check ACME port
    if (this.settings.acme?.enabled && this.settings.acme.port) {
      if (portMappings.has(this.settings.acme.port)) {
        warnings.push(`ACME HTTP challenge port ${this.settings.acme.port} is also used in port ranges`);
      }
    }
    
    return warnings;
  }
}