/**
 * PROXY Protocol Parser
 * Implementation of HAProxy PROXY protocol v1 (text format)
 * Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
 */

import type { IProxyInfo, IProxyParseResult, TProxyProtocol } from './types.js';

/**
 * PROXY protocol parser
 */
export class ProxyProtocolParser {
  static readonly PROXY_V1_SIGNATURE = 'PROXY ';
  static readonly MAX_HEADER_LENGTH = 107; // Max length for v1 header
  static readonly HEADER_TERMINATOR = '\r\n';
  
  /**
   * Parse PROXY protocol v1 header from buffer
   * Returns proxy info and remaining data after header
   */
  static parse(data: Buffer): IProxyParseResult {
    // Check if buffer starts with PROXY signature
    if (!data.toString('ascii', 0, 6).startsWith(this.PROXY_V1_SIGNATURE)) {
      return {
        proxyInfo: null,
        remainingData: data
      };
    }
    
    // Find header terminator
    const headerEndIndex = data.indexOf(this.HEADER_TERMINATOR);
    if (headerEndIndex === -1) {
      // Header incomplete, need more data
      if (data.length > this.MAX_HEADER_LENGTH) {
        // Header too long, invalid
        throw new Error('PROXY protocol header exceeds maximum length');
      }
      return {
        proxyInfo: null,
        remainingData: data
      };
    }
    
    // Extract header line
    const headerLine = data.toString('ascii', 0, headerEndIndex);
    const remainingData = data.slice(headerEndIndex + 2); // Skip \r\n
    
    // Parse header
    const parts = headerLine.split(' ');
    
    if (parts.length < 2) {
      throw new Error(`Invalid PROXY protocol header format: ${headerLine}`);
    }
    
    const [signature, protocol] = parts;
    
    // Validate protocol
    if (!['TCP4', 'TCP6', 'UNKNOWN'].includes(protocol)) {
      throw new Error(`Invalid PROXY protocol: ${protocol}`);
    }
    
    // For UNKNOWN protocol, ignore addresses
    if (protocol === 'UNKNOWN') {
      return {
        proxyInfo: {
          protocol: 'UNKNOWN',
          sourceIP: '',
          sourcePort: 0,
          destinationIP: '',
          destinationPort: 0
        },
        remainingData
      };
    }
    
    // For TCP4/TCP6, we need all 6 parts
    if (parts.length !== 6) {
      throw new Error(`Invalid PROXY protocol header format: ${headerLine}`);
    }
    
    const [, , srcIP, dstIP, srcPort, dstPort] = parts;
    
    // Validate and parse ports
    const sourcePort = parseInt(srcPort, 10);
    const destinationPort = parseInt(dstPort, 10);
    
    if (isNaN(sourcePort) || sourcePort < 0 || sourcePort > 65535) {
      throw new Error(`Invalid source port: ${srcPort}`);
    }
    
    if (isNaN(destinationPort) || destinationPort < 0 || destinationPort > 65535) {
      throw new Error(`Invalid destination port: ${dstPort}`);
    }
    
    // Validate IP addresses
    const protocolType = protocol as TProxyProtocol;
    if (!this.isValidIP(srcIP, protocolType)) {
      throw new Error(`Invalid source IP for ${protocol}: ${srcIP}`);
    }
    
    if (!this.isValidIP(dstIP, protocolType)) {
      throw new Error(`Invalid destination IP for ${protocol}: ${dstIP}`);
    }
    
    return {
      proxyInfo: {
        protocol: protocolType,
        sourceIP: srcIP,
        sourcePort,
        destinationIP: dstIP,
        destinationPort
      },
      remainingData
    };
  }
  
  /**
   * Generate PROXY protocol v1 header
   */
  static generate(info: IProxyInfo): Buffer {
    if (info.protocol === 'UNKNOWN') {
      return Buffer.from(`PROXY UNKNOWN\r\n`, 'ascii');
    }
    
    const header = `PROXY ${info.protocol} ${info.sourceIP} ${info.destinationIP} ${info.sourcePort} ${info.destinationPort}\r\n`;
    
    if (header.length > this.MAX_HEADER_LENGTH) {
      throw new Error('Generated PROXY protocol header exceeds maximum length');
    }
    
    return Buffer.from(header, 'ascii');
  }
  
  /**
   * Validate IP address format
   */
  static isValidIP(ip: string, protocol: TProxyProtocol): boolean {
    if (protocol === 'TCP4') {
      return this.isIPv4(ip);
    } else if (protocol === 'TCP6') {
      return this.isIPv6(ip);
    }
    return false;
  }
  
  /**
   * Check if string is valid IPv4
   */
  static isIPv4(ip: string): boolean {
    const parts = ip.split('.');
    if (parts.length !== 4) return false;
    
    for (const part of parts) {
      const num = parseInt(part, 10);
      if (isNaN(num) || num < 0 || num > 255 || part !== num.toString()) {
        return false;
      }
    }
    return true;
  }
  
  /**
   * Check if string is valid IPv6
   */
  static isIPv6(ip: string): boolean {
    // Basic IPv6 validation
    const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{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}(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}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
    return ipv6Regex.test(ip);
  }
  
  /**
   * Create a connection ID string for tracking
   */
  static createConnectionId(connectionInfo: {
    sourceIp?: string;
    sourcePort?: number;
    destIp?: string;
    destPort?: number;
  }): string {
    const { sourceIp, sourcePort, destIp, destPort } = connectionInfo;
    return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
  }
}