import * as plugins from '../../ts/plugins.js';
import { UnifiedEmailServer } from '../../ts/mail/routing/classes.unified.email.server.js';
import { createSmtpServer } from '../../ts/mail/delivery/smtpserver/index.js';
import type { ISmtpServerOptions } from '../../ts/mail/delivery/smtpserver/interfaces.js';
import type { net } from '../../ts/plugins.js';

export interface ITestServerConfig {
  port: number;
  hostname?: string;
  tlsEnabled?: boolean;
  authRequired?: boolean;
  timeout?: number;
  testCertPath?: string;
  testKeyPath?: string;
  maxConnections?: number;
  size?: number;
  maxRecipients?: number;
}

export interface ITestServer {
  server: any;
  smtpServer: any;
  port: number;
  hostname: string;
  config: ITestServerConfig;
  startTime: number;
}

/**
 * Starts a test SMTP server with the given configuration
 */
export async function startTestServer(config: ITestServerConfig): Promise<ITestServer> {
  // Find a free port if one wasn't specified
  // Using smartnetwork to find an available port in the range 10000-60000
  let port = config.port;
  if (port === undefined || port === 0) {
    const network = new plugins.smartnetwork.Network();
    port = await network.findFreePort(10000, 60000, { randomize: true });
    if (!port) {
      throw new Error('No free ports available in range 10000-60000');
    }
  }

  const serverConfig = {
    port: port, // Use the found free port
    hostname: config.hostname || 'localhost',
    tlsEnabled: config.tlsEnabled || false,
    authRequired: config.authRequired || false,
    timeout: config.timeout || 30000,
    maxConnections: config.maxConnections || 100,
    size: config.size || 10 * 1024 * 1024, // 10MB default
    maxRecipients: config.maxRecipients || 100
  };

  // Create a mock email server for testing
  const mockEmailServer = {
    processEmailByMode: async (emailData: any) => {
      console.log(`📧 [Test Server] Processing email:`, emailData.subject || 'No subject');
      return emailData;
    },
    getRateLimiter: () => {
      // Return a mock rate limiter for testing
      return {
        recordConnection: (_ip: string) => ({ allowed: true, remaining: 100 }),
        checkConnectionLimit: async (_ip: string) => ({ allowed: true, remaining: 100 }),
        checkMessageLimit: (_senderAddress: string, _ip: string, _recipientCount?: number, _pattern?: string, _domain?: string) => ({ allowed: true, remaining: 1000 }),
        checkRecipientLimit: async (_session: any) => ({ allowed: true, remaining: 50 }),
        recordAuthenticationFailure: async (_ip: string) => {},
        recordSyntaxError: async (_ip: string) => {},
        recordCommandError: async (_ip: string) => {},
        recordError: (_key: string) => false, // Return false to not block during tests
        isBlocked: async (_ip: string) => false,
        cleanup: async () => {}
      };
    }
  } as any;

  // Load test certificates
  let key: string;
  let cert: string;
  
  if (serverConfig.tlsEnabled) {
    try {
      const certPath = config.testCertPath || './test/fixtures/test-cert.pem';
      const keyPath = config.testKeyPath || './test/fixtures/test-key.pem';
      
      cert = await plugins.fs.promises.readFile(certPath, 'utf8');
      key = await plugins.fs.promises.readFile(keyPath, 'utf8');
    } catch (error) {
      console.warn('⚠️ Failed to load TLS certificates, falling back to self-signed');
      // Generate self-signed certificate for testing
      const forge = await import('node-forge');
      const pki = forge.default.pki;
      
      // Generate key pair
      const keys = pki.rsa.generateKeyPair(2048);
      
      // Create certificate
      const certificate = pki.createCertificate();
      certificate.publicKey = keys.publicKey;
      certificate.serialNumber = '01';
      certificate.validity.notBefore = new Date();
      certificate.validity.notAfter = new Date();
      certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 1);
      
      const attrs = [{
        name: 'commonName',
        value: serverConfig.hostname
      }];
      certificate.setSubject(attrs);
      certificate.setIssuer(attrs);
      certificate.sign(keys.privateKey);
      
      // Convert to PEM
      cert = pki.certificateToPem(certificate);
      key = pki.privateKeyToPem(keys.privateKey);
    }
  } else {
    // Always provide a self-signed certificate for non-TLS servers
    // This is required by the interface
    const forge = await import('node-forge');
    const pki = forge.default.pki;
    
    // Generate key pair
    const keys = pki.rsa.generateKeyPair(2048);
    
    // Create certificate
    const certificate = pki.createCertificate();
    certificate.publicKey = keys.publicKey;
    certificate.serialNumber = '01';
    certificate.validity.notBefore = new Date();
    certificate.validity.notAfter = new Date();
    certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 1);
    
    const attrs = [{
      name: 'commonName',
      value: serverConfig.hostname
    }];
    certificate.setSubject(attrs);
    certificate.setIssuer(attrs);
    certificate.sign(keys.privateKey);
    
    // Convert to PEM
    cert = pki.certificateToPem(certificate);
    key = pki.privateKeyToPem(keys.privateKey);
  }

  // SMTP server options
  const smtpOptions: ISmtpServerOptions = {
    port: serverConfig.port,
    hostname: serverConfig.hostname,
    key: key,
    cert: cert,
    maxConnections: serverConfig.maxConnections,
    size: serverConfig.size,
    maxRecipients: serverConfig.maxRecipients,
    socketTimeout: serverConfig.timeout,
    connectionTimeout: serverConfig.timeout * 2,
    cleanupInterval: 300000,
    auth: serverConfig.authRequired ? ({
      required: true,
      methods: ['PLAIN', 'LOGIN'] as ('PLAIN' | 'LOGIN' | 'OAUTH2')[],
      validateUser: async (username: string, password: string) => {
        // Test server accepts these credentials
        return username === 'testuser' && password === 'testpass';
      }
    } as any) : undefined
  };

  // Create SMTP server
  const smtpServer = await createSmtpServer(mockEmailServer, smtpOptions);

  // Start the server
  await smtpServer.listen();

  // Wait for server to be ready
  await waitForServerReady(serverConfig.hostname, serverConfig.port);

  console.log(`✅ Test SMTP server started on ${serverConfig.hostname}:${serverConfig.port}`);

  return {
    server: mockEmailServer,
    smtpServer: smtpServer,
    port: serverConfig.port, // Return the port we already know
    hostname: serverConfig.hostname,
    config: serverConfig,
    startTime: Date.now()
  };
}

/**
 * Stops a test SMTP server
 */
export async function stopTestServer(testServer: ITestServer): Promise<void> {
  if (!testServer || !testServer.smtpServer) {
    console.warn('⚠️ No test server to stop');
    return;
  }
  
  try {
    console.log(`🛑 Stopping test SMTP server on ${testServer.hostname}:${testServer.port}`);
    
    // Stop the SMTP server
    if (testServer.smtpServer.close && typeof testServer.smtpServer.close === 'function') {
      await testServer.smtpServer.close();
    }
    
    // Wait for port to be free
    await waitForPortFree(testServer.port);
    
    console.log(`✅ Test SMTP server stopped`);
  } catch (error) {
    console.error('❌ Error stopping test server:', error);
    throw error;
  }
}

/**
 * Wait for server to be ready to accept connections
 */
async function waitForServerReady(hostname: string, port: number, timeout: number = 10000): Promise<void> {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    try {
      await new Promise<void>((resolve, reject) => {
        const socket = plugins.net.createConnection({ port, host: hostname });
        
        socket.on('connect', () => {
          socket.end();
          resolve();
        });
        
        socket.on('error', reject);
        
        setTimeout(() => {
          socket.destroy();
          reject(new Error('Connection timeout'));
        }, 1000);
      });
      
      return; // Server is ready
    } catch {
      // Server not ready yet, wait and retry
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
  
  throw new Error(`Server did not become ready within ${timeout}ms`);
}

/**
 * Wait for port to be free
 */
async function waitForPortFree(port: number, timeout: number = 5000): Promise<void> {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const isFree = await isPortFree(port);
    if (isFree) {
      return;
    }
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  console.warn(`⚠️ Port ${port} still in use after ${timeout}ms`);
}

/**
 * Check if a port is free
 */
async function isPortFree(port: number): Promise<boolean> {
  return new Promise((resolve) => {
    const server = plugins.net.createServer();
    
    server.listen(port, () => {
      server.close(() => resolve(true));
    });
    
    server.on('error', () => resolve(false));
  });
}

/**
 * Get an available port for testing
 */
export async function getAvailablePort(startPort: number = 25000): Promise<number> {
  for (let port = startPort; port < startPort + 1000; port++) {
    if (await isPortFree(port)) {
      return port;
    }
  }
  throw new Error(`No available ports found starting from ${startPort}`);
}

/**
 * Create test email data
 */
export function createTestEmail(options: {
  from?: string;
  to?: string | string[];
  subject?: string;
  text?: string;
  html?: string;
  attachments?: any[];
} = {}): any {
  return {
    from: options.from || 'test@example.com',
    to: options.to || 'recipient@example.com',
    subject: options.subject || 'Test Email',
    text: options.text || 'This is a test email',
    html: options.html || '<p>This is a test email</p>',
    attachments: options.attachments || [],
    date: new Date(),
    messageId: `<${Date.now()}@test.example.com>`
  };
}

/**
 * Simple test server for custom protocol testing
 */
export interface ISimpleTestServer {
  server: any;
  hostname: string;
  port: number;
}

export async function createTestServer(options: {
  onConnection?: (socket: any) => void | Promise<void>;
  port?: number;
  hostname?: string;
}): Promise<ISimpleTestServer> {
  const hostname = options.hostname || 'localhost';
  const port = options.port || await getAvailablePort();
  
  const server = plugins.net.createServer((socket) => {
    if (options.onConnection) {
      const result = options.onConnection(socket);
      if (result && typeof result.then === 'function') {
        result.catch(error => {
          console.error('Error in onConnection handler:', error);
          socket.destroy();
        });
      }
    }
  });
  
  return new Promise((resolve, reject) => {
    server.listen(port, hostname, () => {
      resolve({
        server,
        hostname,
        port
      });
    });
    
    server.on('error', reject);
  });
}