/**
 * Protocol Detector
 * 
 * Simplified protocol detection using the new architecture
 */

import type { IDetectionResult, IDetectionOptions } from './models/detection-types.js';
import type { IConnectionContext } from '../protocols/common/types.js';
import { TlsDetector } from './detectors/tls-detector.js';
import { HttpDetector } from './detectors/http-detector.js';
import { DetectionFragmentManager } from './utils/fragment-manager.js';

/**
 * Main protocol detector class
 */
export class ProtocolDetector {
  private static instance: ProtocolDetector;
  private fragmentManager: DetectionFragmentManager;
  private tlsDetector: TlsDetector;
  private httpDetector: HttpDetector;
  private connectionProtocols: Map<string, { protocol: 'tls' | 'http'; createdAt: number }> = new Map();

  constructor() {
    this.fragmentManager = new DetectionFragmentManager();
    this.tlsDetector = new TlsDetector();
    this.httpDetector = new HttpDetector(this.fragmentManager);
  }
  
  private static getInstance(): ProtocolDetector {
    if (!this.instance) {
      this.instance = new ProtocolDetector();
    }
    return this.instance;
  }
  
  /**
   * Detect protocol from buffer data
   */
  static async detect(buffer: Buffer, options?: IDetectionOptions): Promise<IDetectionResult> {
    return this.getInstance().detectInstance(buffer, options);
  }
  
  private async detectInstance(buffer: Buffer, options?: IDetectionOptions): Promise<IDetectionResult> {
    // Quick sanity check
    if (!buffer || buffer.length === 0) {
      return {
        protocol: 'unknown',
        connectionInfo: { protocol: 'unknown' },
        isComplete: true
      };
    }
    
    // Try TLS detection first (more specific)
    if (this.tlsDetector.canHandle(buffer)) {
      const tlsResult = this.tlsDetector.detect(buffer, options);
      if (tlsResult) {
        return tlsResult;
      }
    }
    
    // Try HTTP detection
    if (this.httpDetector.canHandle(buffer)) {
      const httpResult = this.httpDetector.detect(buffer, options);
      if (httpResult) {
        return httpResult;
      }
    }
    
    // Neither TLS nor HTTP
    return {
      protocol: 'unknown',
      connectionInfo: { protocol: 'unknown' },
      isComplete: true
    };
  }
  
  /**
   * Detect protocol with connection tracking for fragmented data
   * @deprecated Use detectWithContext instead
   */
  static async detectWithConnectionTracking(
    buffer: Buffer,
    connectionId: string,
    options?: IDetectionOptions
  ): Promise<IDetectionResult> {
    // Convert connection ID to context
    const context: IConnectionContext = {
      id: connectionId,
      sourceIp: 'unknown',
      sourcePort: 0,
      destIp: 'unknown',
      destPort: 0,
      timestamp: Date.now()
    };
    
    return this.getInstance().detectWithContextInstance(buffer, context, options);
  }
  
  /**
   * Detect protocol with connection context for fragmented data
   */
  static async detectWithContext(
    buffer: Buffer,
    context: IConnectionContext,
    options?: IDetectionOptions
  ): Promise<IDetectionResult> {
    return this.getInstance().detectWithContextInstance(buffer, context, options);
  }
  
  private async detectWithContextInstance(
    buffer: Buffer,
    context: IConnectionContext,
    options?: IDetectionOptions
  ): Promise<IDetectionResult> {
    // Quick sanity check
    if (!buffer || buffer.length === 0) {
      return {
        protocol: 'unknown',
        connectionInfo: { protocol: 'unknown' },
        isComplete: true
      };
    }
    
    const connectionId = DetectionFragmentManager.createConnectionId(context);
    
    // Check if we already know the protocol for this connection
    const knownEntry = this.connectionProtocols.get(connectionId);
    const knownProtocol = knownEntry?.protocol;

    if (knownProtocol === 'http') {
      const result = this.httpDetector.detectWithContext(buffer, context, options);
      if (result) {
        if (result.isComplete) {
          this.connectionProtocols.delete(connectionId);
        }
        return result;
      }
    } else if (knownProtocol === 'tls') {
      // Handle TLS with fragment accumulation
      const handler = this.fragmentManager.getHandler('tls');
      const fragmentResult = handler.addFragment(connectionId, buffer);
      
      if (fragmentResult.error) {
        handler.complete(connectionId);
        this.connectionProtocols.delete(connectionId);
        return {
          protocol: 'unknown',
          connectionInfo: { protocol: 'unknown' },
          isComplete: true
        };
      }
      
      const result = this.tlsDetector.detect(fragmentResult.buffer!, options);
      if (result) {
        if (result.isComplete) {
          handler.complete(connectionId);
          this.connectionProtocols.delete(connectionId);
        }
        return result;
      }
    }
    
    // If we don't know the protocol yet, try to detect it
    if (!knownProtocol) {
      // First peek to determine protocol type
      if (this.tlsDetector.canHandle(buffer)) {
        this.connectionProtocols.set(connectionId, { protocol: 'tls', createdAt: Date.now() });
        // Handle TLS with fragment accumulation
        const handler = this.fragmentManager.getHandler('tls');
        const fragmentResult = handler.addFragment(connectionId, buffer);
        
        if (fragmentResult.error) {
          handler.complete(connectionId);
          this.connectionProtocols.delete(connectionId);
          return {
            protocol: 'unknown',
            connectionInfo: { protocol: 'unknown' },
            isComplete: true
          };
        }
        
        const result = this.tlsDetector.detect(fragmentResult.buffer!, options);
        if (result) {
          if (result.isComplete) {
            handler.complete(connectionId);
            this.connectionProtocols.delete(connectionId);
          }
          return result;
        }
      }
      
      if (this.httpDetector.canHandle(buffer)) {
        this.connectionProtocols.set(connectionId, { protocol: 'http', createdAt: Date.now() });
        const result = this.httpDetector.detectWithContext(buffer, context, options);
        if (result) {
          if (result.isComplete) {
            this.connectionProtocols.delete(connectionId);
          }
          return result;
        }
      }
    }
    
    // Can't determine protocol
    return {
      protocol: 'unknown',
      connectionInfo: { protocol: 'unknown' },
      isComplete: false,
      bytesNeeded: Math.max(
        this.tlsDetector.getMinimumBytes(),
        this.httpDetector.getMinimumBytes()
      )
    };
  }
  
  /**
   * Clean up resources
   */
  static cleanup(): void {
    this.getInstance().cleanupInstance();
  }
  
  private cleanupInstance(): void {
    this.fragmentManager.cleanup();
    // Remove stale connectionProtocols entries (abandoned handshakes, port scanners)
    const maxAge = 30_000; // 30 seconds
    const now = Date.now();
    for (const [id, entry] of this.connectionProtocols) {
      if (now - entry.createdAt > maxAge) {
        this.connectionProtocols.delete(id);
      }
    }
  }
  
  /**
   * Destroy detector instance
   */
  static destroy(): void {
    this.getInstance().destroyInstance();
    this.instance = null as any;
  }
  
  private destroyInstance(): void {
    this.fragmentManager.destroy();
    this.connectionProtocols.clear();
  }
  
  /**
   * Clean up old connection tracking entries
   * 
   * @param _maxAge Maximum age in milliseconds (default: 30 seconds)
   */
  static cleanupConnections(_maxAge: number = 30000): void {
    this.getInstance().cleanupInstance();
  }
  
  /**
   * Clean up fragments for a specific connection
   */
  static cleanupConnection(context: IConnectionContext): void {
    const instance = this.getInstance();
    const connectionId = DetectionFragmentManager.createConnectionId(context);
    
    // Clean up both TLS and HTTP fragments for this connection
    instance.fragmentManager.getHandler('tls').complete(connectionId);
    instance.fragmentManager.getHandler('http').complete(connectionId);
    
    // Remove from connection protocols tracking
    instance.connectionProtocols.delete(connectionId);
  }
  
  /**
   * Extract domain from connection info
   */
  static extractDomain(connectionInfo: any): string | undefined {
    return connectionInfo.domain || connectionInfo.sni || connectionInfo.host;
  }
  
  /**
   * Create a connection ID from connection parameters
   * @deprecated Use createConnectionContext instead
   */
  static createConnectionId(params: {
    sourceIp?: string;
    sourcePort?: number;
    destIp?: string;
    destPort?: number;
    socketId?: string;
  }): string {
    // If socketId is provided, use it
    if (params.socketId) {
      return params.socketId;
    }
    
    // Otherwise create from connection tuple
    const { sourceIp = 'unknown', sourcePort = 0, destIp = 'unknown', destPort = 0 } = params;
    return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
  }
  
  /**
   * Create a connection context from parameters
   */
  static createConnectionContext(params: {
    sourceIp?: string;
    sourcePort?: number;
    destIp?: string;
    destPort?: number;
    socketId?: string;
  }): IConnectionContext {
    return {
      id: params.socketId,
      sourceIp: params.sourceIp || 'unknown',
      sourcePort: params.sourcePort || 0,
      destIp: params.destIp || 'unknown',
      destPort: params.destPort || 0,
      timestamp: Date.now()
    };
  }
}