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

/**
 * Manages timeouts and inactivity tracking for connections
 */
export class TimeoutManager {
  constructor(private settings: IPortProxySettings) {}
  
  /**
   * Ensure timeout values don't exceed Node.js max safe integer
   */
  public ensureSafeTimeout(timeout: number): number {
    const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
    return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
  }
  
  /**
   * Generate a slightly randomized timeout to prevent thundering herd
   */
  public randomizeTimeout(baseTimeout: number, variationPercent: number = 5): number {
    const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
    const variation = safeBaseTimeout * (variationPercent / 100);
    return this.ensureSafeTimeout(
      safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation
    );
  }
  
  /**
   * Update connection activity timestamp
   */
  public updateActivity(record: IConnectionRecord): void {
    record.lastActivity = Date.now();

    // Clear any inactivity warning
    if (record.inactivityWarningIssued) {
      record.inactivityWarningIssued = false;
    }
  }
  
  /**
   * Calculate effective inactivity timeout based on connection type
   */
  public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
    let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
    
    // For immortal keep-alive connections, use an extremely long timeout
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
      return Number.MAX_SAFE_INTEGER;
    }
    
    // For extended keep-alive connections, apply multiplier
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
      const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
      effectiveTimeout = effectiveTimeout * multiplier;
    }
    
    return this.ensureSafeTimeout(effectiveTimeout);
  }
  
  /**
   * Calculate effective max lifetime based on connection type
   */
  public getEffectiveMaxLifetime(record: IConnectionRecord): number {
    // Use domain-specific timeout if available
    const baseTimeout = record.domainConfig?.connectionTimeout || 
                        this.settings.maxConnectionLifetime || 
                        86400000; // 24 hours default
    
    // For immortal keep-alive connections, use an extremely long lifetime
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
      return Number.MAX_SAFE_INTEGER;
    }
    
    // For extended keep-alive connections, use the extended lifetime setting
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
      return this.ensureSafeTimeout(
        this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
      );
    }
    
    // Apply randomization if enabled
    if (this.settings.enableRandomizedTimeouts) {
      return this.randomizeTimeout(baseTimeout);
    }
    
    return this.ensureSafeTimeout(baseTimeout);
  }
  
  /**
   * Setup connection timeout
   * @returns The cleanup timer
   */
  public setupConnectionTimeout(
    record: IConnectionRecord, 
    onTimeout: (record: IConnectionRecord, reason: string) => void
  ): NodeJS.Timeout {
    // Clear any existing timer
    if (record.cleanupTimer) {
      clearTimeout(record.cleanupTimer);
    }
    
    // Calculate effective timeout
    const effectiveLifetime = this.getEffectiveMaxLifetime(record);
    
    // Set up the timeout
    const timer = setTimeout(() => {
      // Call the provided callback
      onTimeout(record, 'connection_timeout');
    }, effectiveLifetime);
    
    // Make sure timeout doesn't keep the process alive
    if (timer.unref) {
      timer.unref();
    }
    
    return timer;
  }
  
  /**
   * Check for inactivity on a connection
   * @returns Object with check results
   */
  public checkInactivity(record: IConnectionRecord): {
    isInactive: boolean;
    shouldWarn: boolean;
    inactivityTime: number;
    effectiveTimeout: number;
  } {
    // Skip for connections with inactivity check disabled
    if (this.settings.disableInactivityCheck) {
      return {
        isInactive: false,
        shouldWarn: false,
        inactivityTime: 0,
        effectiveTimeout: 0
      };
    }
    
    // Skip for immortal keep-alive connections
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
      return {
        isInactive: false,
        shouldWarn: false,
        inactivityTime: 0,
        effectiveTimeout: 0
      };
    }
    
    const now = Date.now();
    const inactivityTime = now - record.lastActivity;
    const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
    
    // Check if inactive
    const isInactive = inactivityTime > effectiveTimeout;
    
    // For keep-alive connections, we should warn first
    const shouldWarn = record.hasKeepAlive && 
                       isInactive && 
                       !record.inactivityWarningIssued;
    
    return {
      isInactive,
      shouldWarn,
      inactivityTime,
      effectiveTimeout
    };
  }
  
  /**
   * Apply socket timeout settings
   */
  public applySocketTimeouts(record: IConnectionRecord): void {
    // Skip for immortal keep-alive connections
    if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
      // Disable timeouts completely for immortal connections
      record.incoming.setTimeout(0);
      if (record.outgoing) {
        record.outgoing.setTimeout(0);
      }
      return;
    }
    
    // Apply normal timeouts
    const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
    record.incoming.setTimeout(timeout);
    if (record.outgoing) {
      record.outgoing.setTimeout(timeout);
    }
  }
}