import * as plugins from './plugins.js';
import type { IPortProxySettings } from './classes.pp.interfaces.js';
import { SniHandler } from './classes.pp.snihandler.js';

/**
 * Interface for connection information used for SNI extraction
 */
interface IConnectionInfo {
  sourceIp: string;
  sourcePort: number;
  destIp: string;
  destPort: number;
}

/**
 * Manages TLS-related operations including SNI extraction and validation
 */
export class TlsManager {
  constructor(private settings: IPortProxySettings) {}
  
  /**
   * Check if a data chunk appears to be a TLS handshake
   */
  public isTlsHandshake(chunk: Buffer): boolean {
    return SniHandler.isTlsHandshake(chunk);
  }
  
  /**
   * Check if a data chunk appears to be a TLS ClientHello
   */
  public isClientHello(chunk: Buffer): boolean {
    return SniHandler.isClientHello(chunk);
  }
  
  /**
   * Extract Server Name Indication (SNI) from TLS handshake
   */
  public extractSNI(
    chunk: Buffer, 
    connInfo: IConnectionInfo, 
    previousDomain?: string
  ): string | undefined {
    // Use the SniHandler to process the TLS packet
    return SniHandler.processTlsPacket(
      chunk,
      connInfo,
      this.settings.enableTlsDebugLogging || false,
      previousDomain
    );
  }
  
  /**
   * Handle session resumption attempts
   */
  public handleSessionResumption(
    chunk: Buffer, 
    connectionId: string,
    hasSNI: boolean
  ): { shouldBlock: boolean; reason?: string } {
    // Skip if session tickets are allowed
    if (this.settings.allowSessionTicket !== false) {
      return { shouldBlock: false };
    }
    
    // Check for session resumption attempt
    const resumptionInfo = SniHandler.hasSessionResumption(
      chunk,
      this.settings.enableTlsDebugLogging || false
    );
    
    // If this is a resumption attempt without SNI, block it
    if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
      if (this.settings.enableTlsDebugLogging) {
        console.log(
          `[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
          `Terminating connection to force new TLS handshake.`
        );
      }
      return { 
        shouldBlock: true, 
        reason: 'session_ticket_blocked' 
      };
    }
    
    return { shouldBlock: false };
  }
  
  /**
   * Check for SNI mismatch during renegotiation
   */
  public checkRenegotiationSNI(
    chunk: Buffer,
    connInfo: IConnectionInfo,
    expectedDomain: string,
    connectionId: string
  ): { hasMismatch: boolean; extractedSNI?: string } {
    // Only process if this looks like a TLS ClientHello
    if (!this.isClientHello(chunk)) {
      return { hasMismatch: false };
    }
    
    try {
      // Extract SNI with renegotiation support
      const newSNI = SniHandler.extractSNIWithResumptionSupport(
        chunk,
        connInfo,
        this.settings.enableTlsDebugLogging || false
      );

      // Skip if no SNI was found
      if (!newSNI) return { hasMismatch: false };

      // Check for SNI mismatch
      if (newSNI !== expectedDomain) {
        if (this.settings.enableTlsDebugLogging) {
          console.log(
            `[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
            `Terminating connection - SNI domain switching is not allowed.`
          );
        }
        return { hasMismatch: true, extractedSNI: newSNI };
      } else if (this.settings.enableTlsDebugLogging) {
        console.log(
          `[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`
        );
      }
    } catch (err) {
      console.log(
        `[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`
      );
    }
    
    return { hasMismatch: false };
  }
  
  /**
   * Create a renegotiation handler function for a connection
   */
  public createRenegotiationHandler(
    connectionId: string,
    lockedDomain: string,
    connInfo: IConnectionInfo,
    onMismatch: (connectionId: string, reason: string) => void
  ): (chunk: Buffer) => void {
    return (chunk: Buffer) => {
      const result = this.checkRenegotiationSNI(chunk, connInfo, lockedDomain, connectionId);
      if (result.hasMismatch) {
        onMismatch(connectionId, 'sni_mismatch');
      }
    };
  }
  
  /**
   * Analyze TLS connection for browser fingerprinting
   * This helps identify browser vs non-browser connections
   */
  public analyzeClientHello(chunk: Buffer): { 
    isBrowserConnection: boolean; 
    isRenewal: boolean;
    hasSNI: boolean;
  } {
    // Default result
    const result = { 
      isBrowserConnection: false, 
      isRenewal: false,
      hasSNI: false
    };
    
    try {
      // Check if it's a ClientHello
      if (!this.isClientHello(chunk)) {
        return result;
      }
      
      // Check for session resumption
      const resumptionInfo = SniHandler.hasSessionResumption(
        chunk,
        this.settings.enableTlsDebugLogging || false
      );
      
      // Extract SNI
      const sni = SniHandler.extractSNI(
        chunk,
        this.settings.enableTlsDebugLogging || false
      );
      
      // Update result
      result.isRenewal = resumptionInfo.isResumption;
      result.hasSNI = !!sni;
      
      // Browsers typically:
      // 1. Send SNI extension
      // 2. Have a variety of extensions (ALPN, etc.)
      // 3. Use standard cipher suites
      // ...more complex heuristics could be implemented here
      
      // Simple heuristic: presence of SNI suggests browser
      result.isBrowserConnection = !!sni; 
      
      return result;
    } catch (err) {
      console.log(`Error analyzing ClientHello: ${err}`);
      return result;
    }
  }
}