/**
 * SMTP Security Handler
 * Responsible for security aspects including IP reputation checking, 
 * email validation, and authentication
 */

import * as plugins from '../../../plugins.js';
import type { ISmtpSession, ISmtpAuth } from './interfaces.js';
import type { ISecurityHandler, ISmtpServer } from './interfaces.js';
import { SmtpLogger } from './utils/logging.js';
import { SecurityEventType, SecurityLogLevel } from './constants.js';
import { isValidEmail } from './utils/validation.js';
import { getSocketDetails, getTlsDetails } from './utils/helpers.js';
import { IPReputationChecker } from '../../../security/classes.ipreputationchecker.js';

/**
 * Interface for IP denylist entry
 */
interface IIpDenylistEntry {
  ip: string;
  reason: string;
  expiresAt?: number;
}

/**
 * Handles security aspects for SMTP server
 */
export class SecurityHandler implements ISecurityHandler {
  /**
   * Reference to the SMTP server instance
   */
  private smtpServer: ISmtpServer;
  
  /**
   * IP reputation checker service
   */
  private ipReputationService: IPReputationChecker;
  
  /**
   * Simple in-memory IP denylist
   */
  private ipDenylist: IIpDenylistEntry[] = [];
  
  /**
   * Cleanup interval timer
   */
  private cleanupInterval: NodeJS.Timeout | null = null;
  
  /**
   * Creates a new security handler
   * @param smtpServer - SMTP server instance
   */
  constructor(smtpServer: ISmtpServer) {
    this.smtpServer = smtpServer;
    
    // Initialize IP reputation checker
    this.ipReputationService = new IPReputationChecker();
    
    // Clean expired denylist entries periodically
    this.cleanupInterval = setInterval(() => this.cleanExpiredDenylistEntries(), 60000); // Every minute
  }
  
  /**
   * Check IP reputation for a connection
   * @param socket - Client socket
   * @returns Promise that resolves to true if IP is allowed, false if blocked
   */
  public async checkIpReputation(socket: plugins.net.Socket | plugins.tls.TLSSocket): Promise<boolean> {
    const socketDetails = getSocketDetails(socket);
    const ip = socketDetails.remoteAddress;
    
    // Check local denylist first
    if (this.isIpDenylisted(ip)) {
      // Log the blocked connection
      this.logSecurityEvent(
        SecurityEventType.IP_REPUTATION,
        SecurityLogLevel.WARN,
        `Connection blocked from denylisted IP: ${ip}`,
        { reason: this.getDenylistReason(ip) }
      );
      
      return false;
    }
    
    // Check with IP reputation service
    if (!this.ipReputationService) {
      return true;
    }
    
    try {
      // Check with IP reputation service
      const reputationResult = await this.ipReputationService.checkReputation(ip);
      
      // Block if score is below HIGH_RISK threshold (20) or if it's spam/proxy/tor/vpn
      const isBlocked = reputationResult.score < 20 || 
                       reputationResult.isSpam || 
                       reputationResult.isTor || 
                       reputationResult.isProxy;
      
      if (isBlocked) {
        // Add to local denylist temporarily
        const reason = reputationResult.isSpam ? 'spam' : 
                      reputationResult.isTor ? 'tor' : 
                      reputationResult.isProxy ? 'proxy' : 
                      `low reputation score: ${reputationResult.score}`;
        this.addToDenylist(ip, reason, 3600000); // 1 hour
        
        // Log the blocked connection
        this.logSecurityEvent(
          SecurityEventType.IP_REPUTATION,
          SecurityLogLevel.WARN,
          `Connection blocked by reputation service: ${ip}`,
          { 
            reason,
            score: reputationResult.score,
            isSpam: reputationResult.isSpam,
            isTor: reputationResult.isTor,
            isProxy: reputationResult.isProxy,
            isVPN: reputationResult.isVPN
          }
        );
        
        return false;
      }
      
      // Log the allowed connection
      this.logSecurityEvent(
        SecurityEventType.IP_REPUTATION,
        SecurityLogLevel.INFO,
        `IP reputation check passed: ${ip}`,
        { 
          score: reputationResult.score,
          country: reputationResult.country,
          org: reputationResult.org
        }
      );
      
      return true;
    } catch (error) {
      // Log the error
      SmtpLogger.error(`IP reputation check error: ${error instanceof Error ? error.message : String(error)}`, {
        ip,
        error: error instanceof Error ? error : new Error(String(error))
      });
      
      // Allow the connection on error (fail open)
      return true;
    }
  }
  
  /**
   * Validate an email address
   * @param email - Email address to validate
   * @returns Whether the email address is valid
   */
  public isValidEmail(email: string): boolean {
    return isValidEmail(email);
  }
  
  /**
   * Validate authentication credentials
   * @param auth - Authentication credentials
   * @returns Promise that resolves to true if authenticated
   */
  public async authenticate(auth: ISmtpAuth): Promise<boolean> {
    const { username, password } = auth;
    // Get auth options from server
    const options = this.smtpServer.getOptions();
    const authOptions = options.auth;
    
    // Check if authentication is enabled
    if (!authOptions) {
      this.logSecurityEvent(
        SecurityEventType.AUTHENTICATION,
        SecurityLogLevel.WARN,
        'Authentication attempt when auth is disabled',
        { username }
      );
      
      return false;
    }
    
    // Note: Method validation and TLS requirement checks would need to be done
    // at the caller level since the interface doesn't include session/method info
    
    try {
      let authenticated = false;
      
      // Use custom validation function if provided
      if ((authOptions as any).validateUser) {
        authenticated = await (authOptions as any).validateUser(username, password);
      } else {
        // Default behavior - no authentication
        authenticated = false;
      }
      
      // Log the authentication result
      this.logSecurityEvent(
        SecurityEventType.AUTHENTICATION,
        authenticated ? SecurityLogLevel.INFO : SecurityLogLevel.WARN,
        authenticated ? 'Authentication successful' : 'Authentication failed',
        { username }
      );
      
      return authenticated;
    } catch (error) {
      // Log authentication error
      this.logSecurityEvent(
        SecurityEventType.AUTHENTICATION,
        SecurityLogLevel.ERROR,
        `Authentication error: ${error instanceof Error ? error.message : String(error)}`,
        { username, error: error instanceof Error ? error.message : String(error) }
      );
      
      return false;
    }
  }
  
  /**
   * Log a security event
   * @param event - Event type
   * @param level - Log level
   * @param details - Event details
   */
  public logSecurityEvent(event: string, level: string, message: string, details: Record<string, any>): void {
    SmtpLogger.logSecurityEvent(
      level as SecurityLogLevel,
      event as SecurityEventType,
      message,
      details,
      details.ip,
      details.domain,
      details.success
    );
  }
  
  /**
   * Add an IP to the denylist
   * @param ip - IP address
   * @param reason - Reason for denylisting
   * @param duration - Duration in milliseconds (optional, indefinite if not specified)
   */
  private addToDenylist(ip: string, reason: string, duration?: number): void {
    // Remove existing entry if present
    this.ipDenylist = this.ipDenylist.filter(entry => entry.ip !== ip);
    
    // Create new entry
    const entry: IIpDenylistEntry = {
      ip,
      reason,
      expiresAt: duration ? Date.now() + duration : undefined
    };
    
    // Add to denylist
    this.ipDenylist.push(entry);
    
    // Log the action
    this.logSecurityEvent(
      SecurityEventType.ACCESS_CONTROL,
      SecurityLogLevel.INFO,
      `Added IP to denylist: ${ip}`,
      { 
        ip,
        reason,
        duration: duration ? `${duration / 1000} seconds` : 'indefinite'
      }
    );
  }
  
  /**
   * Check if an IP is denylisted
   * @param ip - IP address
   * @returns Whether the IP is denylisted
   */
  private isIpDenylisted(ip: string): boolean {
    const entry = this.ipDenylist.find(e => e.ip === ip);
    
    if (!entry) {
      return false;
    }
    
    // Check if entry has expired
    if (entry.expiresAt && entry.expiresAt < Date.now()) {
      // Remove expired entry
      this.ipDenylist = this.ipDenylist.filter(e => e !== entry);
      return false;
    }
    
    return true;
  }
  
  /**
   * Get the reason an IP was denylisted
   * @param ip - IP address
   * @returns Reason for denylisting or undefined if not denylisted
   */
  private getDenylistReason(ip: string): string | undefined {
    const entry = this.ipDenylist.find(e => e.ip === ip);
    return entry?.reason;
  }
  
  /**
   * Clean expired denylist entries
   */
  private cleanExpiredDenylistEntries(): void {
    const now = Date.now();
    const initialCount = this.ipDenylist.length;
    
    this.ipDenylist = this.ipDenylist.filter(entry => {
      return !entry.expiresAt || entry.expiresAt > now;
    });
    
    const removedCount = initialCount - this.ipDenylist.length;
    
    if (removedCount > 0) {
      this.logSecurityEvent(
        SecurityEventType.ACCESS_CONTROL,
        SecurityLogLevel.INFO,
        `Cleaned up ${removedCount} expired denylist entries`,
        { remainingCount: this.ipDenylist.length }
      );
    }
  }
  
  /**
   * Clean up resources
   */
  public destroy(): void {
    // Clear the cleanup interval
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
    
    // Clear the denylist
    this.ipDenylist = [];
    
    // Clean up IP reputation service if it has a destroy method
    if (this.ipReputationService && typeof (this.ipReputationService as any).destroy === 'function') {
      (this.ipReputationService as any).destroy();
    }
    
    SmtpLogger.debug('SecurityHandler destroyed');
  }
}