import * as plugins from '../../plugins.js';
import type { IRouteConfig, IRouteContext } from '../../proxies/smart-proxy/models/route-types.js';
import type {
  IIpValidationResult,
  IIpConnectionInfo,
  ISecurityLogger,
  IRateLimitInfo
} from './security-utils.js';
import {
  isIPAuthorized,
  checkMaxConnections,
  checkConnectionRate,
  trackConnection,
  removeConnection,
  cleanupExpiredRateLimits,
  parseBasicAuthHeader
} from './security-utils.js';

/**
 * Shared SecurityManager for use across proxy components
 * Handles IP tracking, rate limiting, and authentication
 */
export class SharedSecurityManager {
  // IP connection tracking
  private connectionsByIP: Map<string, IIpConnectionInfo> = new Map();
  
  // Route-specific rate limiting
  private rateLimits: Map<string, Map<string, IRateLimitInfo>> = new Map();
  
  // Cache IP filtering results to avoid constant regex matching
  private ipFilterCache: Map<string, Map<string, boolean>> = new Map();
  
  // Default limits
  private maxConnectionsPerIP: number;
  private connectionRateLimitPerMinute: number;
  
  // Cache cleanup interval
  private cleanupInterval: NodeJS.Timeout | null = null;
  
  /**
   * Create a new SharedSecurityManager
   * 
   * @param options - Configuration options
   * @param logger - Logger instance
   */
  constructor(options: {
    maxConnectionsPerIP?: number;
    connectionRateLimitPerMinute?: number;
    cleanupIntervalMs?: number;
    routes?: IRouteConfig[];
  }, private logger?: ISecurityLogger) {
    this.maxConnectionsPerIP = options.maxConnectionsPerIP || 100;
    this.connectionRateLimitPerMinute = options.connectionRateLimitPerMinute || 300;
    
    // Set up logger with defaults if not provided
    this.logger = logger || {
      info: console.log,
      warn: console.warn,
      error: console.error
    };
    
    // Set up cache cleanup interval
    const cleanupInterval = options.cleanupIntervalMs || 60000; // Default: 1 minute
    this.cleanupInterval = setInterval(() => {
      this.cleanupCaches();
    }, cleanupInterval);
    
    // Don't keep the process alive just for cleanup
    if (this.cleanupInterval.unref) {
      this.cleanupInterval.unref();
    }
  }
  
  /**
   * Get connections count by IP
   * 
   * @param ip - The IP address to check
   * @returns Number of connections from this IP
   */
  public getConnectionCountByIP(ip: string): number {
    return this.connectionsByIP.get(ip)?.connections.size || 0;
  }
  
  /**
   * Track connection by IP
   * 
   * @param ip - The IP address to track
   * @param connectionId - The connection ID to associate
   */
  public trackConnectionByIP(ip: string, connectionId: string): void {
    trackConnection(ip, connectionId, this.connectionsByIP);
  }
  
  /**
   * Remove connection tracking for an IP
   * 
   * @param ip - The IP address to update
   * @param connectionId - The connection ID to remove
   */
  public removeConnectionByIP(ip: string, connectionId: string): void {
    removeConnection(ip, connectionId, this.connectionsByIP);
  }
  
  /**
   * Check if IP is authorized based on route security settings
   * 
   * @param ip - The IP address to check
   * @param allowedIPs - List of allowed IP patterns
   * @param blockedIPs - List of blocked IP patterns
   * @returns Whether the IP is authorized
   */
  public isIPAuthorized(
    ip: string,
    allowedIPs: string[] = ['*'],
    blockedIPs: string[] = []
  ): boolean {
    return isIPAuthorized(ip, allowedIPs, blockedIPs);
  }
  
  /**
   * Validate IP against rate limits and connection limits
   * 
   * @param ip - The IP address to validate
   * @returns Result with allowed status and reason if blocked
   */
  public validateIP(ip: string): IIpValidationResult {
    // Check connection count limit
    const connectionResult = checkMaxConnections(
      ip, 
      this.connectionsByIP, 
      this.maxConnectionsPerIP
    );
    if (!connectionResult.allowed) {
      return connectionResult;
    }
    
    // Check connection rate limit
    const rateResult = checkConnectionRate(
      ip, 
      this.connectionsByIP, 
      this.connectionRateLimitPerMinute
    );
    if (!rateResult.allowed) {
      return rateResult;
    }
    
    return { allowed: true };
  }
  
  /**
   * Check if a client is allowed to access a specific route
   * 
   * @param route - The route to check
   * @param context - The request context
   * @returns Whether access is allowed
   */
  public isAllowed(route: IRouteConfig, context: IRouteContext): boolean {
    if (!route.security) {
      return true; // No security restrictions
    }
    
    // --- IP filtering ---
    if (!this.isClientIpAllowed(route, context.clientIp)) {
      this.logger?.debug?.(`IP ${context.clientIp} is blocked for route ${route.name || 'unnamed'}`);
      return false;
    }
    
    // --- Rate limiting ---
    if (route.security.rateLimit?.enabled && !this.isWithinRateLimit(route, context)) {
      this.logger?.debug?.(`Rate limit exceeded for route ${route.name || 'unnamed'}`);
      return false;
    }
    
    return true;
  }
  
  /**
   * Check if a client IP is allowed for a route
   * 
   * @param route - The route to check
   * @param clientIp - The client IP
   * @returns Whether the IP is allowed
   */
  private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
    if (!route.security) {
      return true; // No security restrictions
    }
    
    const routeId = route.id || route.name || 'unnamed';
    
    // Check cache first
    if (!this.ipFilterCache.has(routeId)) {
      this.ipFilterCache.set(routeId, new Map());
    }
    
    const routeCache = this.ipFilterCache.get(routeId)!;
    if (routeCache.has(clientIp)) {
      return routeCache.get(clientIp)!;
    }
    
    // Check IP against route security settings
    const ipAllowList = route.security.ipAllowList;
    const ipBlockList = route.security.ipBlockList;
    
    const allowed = this.isIPAuthorized(clientIp, ipAllowList, ipBlockList);
    
    // Cache the result
    routeCache.set(clientIp, allowed);
    
    return allowed;
  }
  
  /**
   * Check if request is within rate limit
   * 
   * @param route - The route to check
   * @param context - The request context
   * @returns Whether the request is within rate limit
   */
  private isWithinRateLimit(route: IRouteConfig, context: IRouteContext): boolean {
    if (!route.security?.rateLimit?.enabled) {
      return true;
    }
    
    const rateLimit = route.security.rateLimit;
    const routeId = route.id || route.name || 'unnamed';
    
    // Determine rate limit key (by IP, path, or header)
    let key = context.clientIp; // Default to IP
    
    if (rateLimit.keyBy === 'path' && context.path) {
      key = `${context.clientIp}:${context.path}`;
    } else if (rateLimit.keyBy === 'header' && rateLimit.headerName && context.headers) {
      const headerValue = context.headers[rateLimit.headerName.toLowerCase()];
      if (headerValue) {
        key = `${context.clientIp}:${headerValue}`;
      }
    }
    
    // Get or create rate limit tracking for this route
    if (!this.rateLimits.has(routeId)) {
      this.rateLimits.set(routeId, new Map());
    }
    
    const routeLimits = this.rateLimits.get(routeId)!;
    const now = Date.now();
    
    // Get or create rate limit tracking for this key
    let limit = routeLimits.get(key);
    if (!limit || limit.expiry < now) {
      // Create new rate limit or reset expired one
      limit = {
        count: 1,
        expiry: now + (rateLimit.window * 1000)
      };
      routeLimits.set(key, limit);
      return true;
    }
    
    // Increment the counter
    limit.count++;
    
    // Check if rate limit is exceeded
    return limit.count <= rateLimit.maxRequests;
  }
  
  /**
   * Validate HTTP Basic Authentication
   * 
   * @param route - The route to check
   * @param authHeader - The Authorization header
   * @returns Whether authentication is valid
   */
  public validateBasicAuth(route: IRouteConfig, authHeader?: string): boolean {
    // Skip if basic auth not enabled for route
    if (!route.security?.basicAuth?.enabled) {
      return true;
    }
    
    // No auth header means auth failed
    if (!authHeader) {
      return false;
    }
    
    // Parse auth header
    const credentials = parseBasicAuthHeader(authHeader);
    if (!credentials) {
      return false;
    }
    
    // Check credentials against configured users
    const { username, password } = credentials;
    const users = route.security.basicAuth.users;
    
    return users.some(user => 
      user.username === username && user.password === password
    );
  }
  
  /**
   * Clean up caches to prevent memory leaks
   */
  private cleanupCaches(): void {
    // Clean up rate limits
    cleanupExpiredRateLimits(this.rateLimits, this.logger);
    
    // IP filter cache doesn't need cleanup (tied to routes)
  }
  
  /**
   * Clear all IP tracking data (for shutdown)
   */
  public clearIPTracking(): void {
    this.connectionsByIP.clear();
    this.rateLimits.clear();
    this.ipFilterCache.clear();
    
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
  }
  
  /**
   * Update routes for security checking
   * 
   * @param routes - New routes to use
   */
  public setRoutes(routes: IRouteConfig[]): void {
    // Only clear the IP filter cache - route-specific
    this.ipFilterCache.clear();
  }
}