import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.ts'
import type { ITestServer } from '../../helpers/server.loader.js';

const TEST_PORT = 2525;
let testServer: ITestServer;

// Helper to wait for SMTP response
const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise<string> => {
  return new Promise((resolve, reject) => {
    let buffer = '';
    const timer = setTimeout(() => {
      socket.removeListener('data', handler);
      reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`));
    }, timeout);
    
    const handler = (data: Buffer) => {
      buffer += data.toString();
      const lines = buffer.split('\r\n');
      
      for (const line of lines) {
        if (expectedCode) {
          if (line.startsWith(expectedCode + ' ')) {
            clearTimeout(timer);
            socket.removeListener('data', handler);
            resolve(buffer);
            return;
          }
        } else {
          // Look for any complete response
          if (line.match(/^\d{3} /)) {
            clearTimeout(timer);
            socket.removeListener('data', handler);
            resolve(buffer);
            return;
          }
        }
      }
    };
    
    socket.on('data', handler);
  });
};

tap.test('setup - start test server', async (toolsArg) => {
  testServer = await startTestServer({ port: TEST_PORT });
  await toolsArg.delayFor(1000);
});

tap.test('Authorization - Valid sender domain', async (tools) => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 30000
  });
  
  try {
    // Wait for greeting
    await waitForResponse(socket, '220');
    
    // Send EHLO
    socket.write('EHLO test.example.com\r\n');
    await waitForResponse(socket, '250');
    
    // Use valid sender domain with proper format
    socket.write('MAIL FROM:<test@example.com>\r\n');
    const mailResponse = await waitForResponse(socket);
    
    if (mailResponse.startsWith('250')) {
      // Try recipient
      socket.write('RCPT TO:<recipient@example.com>\r\n');
      const rcptResponse = await waitForResponse(socket);
      
      // Valid sender should be accepted or require auth
      const accepted = rcptResponse.startsWith('250');
      const authRequired = rcptResponse.startsWith('530');
      console.log(`Valid sender domain: ${accepted ? 'accepted' : authRequired ? 'auth required' : 'rejected'}`);
      
      expect(accepted || authRequired).toEqual(true);
    } else {
      // Mail from rejected - could be due to auth requirement
      const authRequired = mailResponse.startsWith('530');
      console.log(`MAIL FROM requires auth: ${authRequired}`);
      expect(authRequired || mailResponse.startsWith('250')).toEqual(true);
    }
    
    socket.write('QUIT\r\n');
    await waitForResponse(socket, '221').catch(() => {});
  } finally {
    socket.destroy();
  }
});

tap.test('Authorization - External sender domain', async (tools) => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 30000
  });
  
  try {
    // Wait for greeting
    await waitForResponse(socket, '220');
    
    // Send EHLO
    socket.write('EHLO external.com\r\n');
    await waitForResponse(socket, '250');
    
    // Use external sender domain
    socket.write('MAIL FROM:<test@external.com>\r\n');
    const mailResponse = await waitForResponse(socket);
    
    if (mailResponse.startsWith('250')) {
      // Try recipient
      socket.write('RCPT TO:<recipient@example.com>\r\n');
      const rcptResponse = await waitForResponse(socket);
      
      // Check response
      const accepted = rcptResponse.startsWith('250');
      const authRequired = rcptResponse.startsWith('530');
      const rejected = rcptResponse.startsWith('550') || rcptResponse.startsWith('553');
      
      console.log(`External sender: accepted=${accepted}, authRequired=${authRequired}, rejected=${rejected}`);
      expect(accepted || authRequired || rejected).toEqual(true);
    } else {
      // Check if auth required or rejected
      const authRequired = mailResponse.startsWith('530');
      const rejected = mailResponse.startsWith('550') || mailResponse.startsWith('553');
      
      console.log(`External sender ${authRequired ? 'requires authentication' : rejected ? 'rejected by policy' : 'error'}`);
      expect(authRequired || rejected).toEqual(true);
    }
    
    socket.write('QUIT\r\n');
    await waitForResponse(socket, '221').catch(() => {});
  } finally {
    socket.destroy();
  }
});

tap.test('Authorization - Relay attempt rejection', async (tools) => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 30000
  });
  
  try {
    // Wait for greeting
    await waitForResponse(socket, '220');
    
    // Send EHLO
    socket.write('EHLO external.com\r\n');
    await waitForResponse(socket, '250');
    
    // External sender
    socket.write('MAIL FROM:<test@external.com>\r\n');
    const mailResponse = await waitForResponse(socket);
    
    if (mailResponse.startsWith('250')) {
      // Try to relay to another external domain (should be rejected)
      socket.write('RCPT TO:<recipient@another-external.com>\r\n');
      const rcptResponse = await waitForResponse(socket);
      
      // Relay attempt should be rejected or accepted (test mode)
      const rejected = rcptResponse.startsWith('550') || 
                      rcptResponse.startsWith('553') || 
                      rcptResponse.startsWith('530') ||
                      rcptResponse.startsWith('554');
      const accepted = rcptResponse.startsWith('250');
      
      console.log(`Relay attempt ${rejected ? 'properly rejected' : accepted ? 'accepted (test mode)' : 'error'}`);
      // In production, relay should be rejected. In test mode, it might be accepted
      expect(rejected || accepted).toEqual(true);
      
      if (accepted) {
        console.log('⚠️ WARNING: Server accepted relay attempt - ensure relay restrictions are properly configured in production');
      }
    } else {
      // MAIL FROM already rejected
      console.log('External sender rejected at MAIL FROM');
      expect(mailResponse.startsWith('530') || mailResponse.startsWith('550')).toEqual(true);
    }
    
    socket.write('QUIT\r\n');
    await waitForResponse(socket, '221').catch(() => {});
  } finally {
    socket.destroy();
  }
});

tap.test('Authorization - IP-based restrictions', async (tools) => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 30000
  });
  
  try {
    // Wait for greeting
    await waitForResponse(socket, '220');
    
    // Use IP address in EHLO
    socket.write('EHLO [127.0.0.1]\r\n');
    await waitForResponse(socket, '250');
    
    // Use proper email format
    socket.write('MAIL FROM:<test@example.com>\r\n');
    const mailResponse = await waitForResponse(socket);
    
    if (mailResponse.startsWith('250')) {
      socket.write('RCPT TO:<recipient@example.com>\r\n');
      const rcptResponse = await waitForResponse(socket);
      
      // Localhost IP should typically be accepted
      const accepted = rcptResponse.startsWith('250');
      const rejected = rcptResponse.startsWith('550') || rcptResponse.startsWith('553');
      const authRequired = rcptResponse.startsWith('530');
      
      console.log(`IP-based authorization: ${accepted ? 'accepted' : rejected ? 'rejected' : 'auth required'}`);
      expect(accepted || rejected || authRequired).toEqual(true); // Any is valid based on server config
    } else {
      // Check if auth required
      const authRequired = mailResponse.startsWith('530');
      console.log(`MAIL FROM ${authRequired ? 'requires auth' : 'rejected'}`);
      expect(authRequired || mailResponse.startsWith('250')).toEqual(true);
    }
    
    socket.write('QUIT\r\n');
    await waitForResponse(socket, '221').catch(() => {});
  } finally {
    socket.destroy();
  }
});

tap.test('Authorization - Case sensitivity in addresses', async (tools) => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 30000
  });
  
  try {
    // Wait for greeting
    await waitForResponse(socket, '220');
    
    // Send EHLO
    socket.write('EHLO test.example.com\r\n');
    await waitForResponse(socket, '250');
    
    // Use mixed case in email address with proper domain
    socket.write('MAIL FROM:<TeSt@ExAmPlE.cOm>\r\n');
    const mailResponse = await waitForResponse(socket);
    
    if (mailResponse.startsWith('250')) {
      // Mixed case recipient
      socket.write('RCPT TO:<ReCiPiEnT@ExAmPlE.cOm>\r\n');
      const rcptResponse = await waitForResponse(socket);
      
      // Email addresses should be case-insensitive
      const accepted = rcptResponse.startsWith('250');
      const authRequired = rcptResponse.startsWith('530');
      console.log(`Mixed case addresses ${accepted ? 'accepted' : authRequired ? 'auth required' : 'rejected'}`);
      
      expect(accepted || authRequired).toEqual(true);
    } else {
      // Check if auth required
      const authRequired = mailResponse.startsWith('530');
      console.log(`MAIL FROM ${authRequired ? 'requires auth' : 'rejected'}`);
      expect(authRequired || mailResponse.startsWith('250')).toEqual(true);
    }
    
    socket.write('QUIT\r\n');
    await waitForResponse(socket, '221').catch(() => {});
  } finally {
    socket.destroy();
  }
});

tap.test('cleanup - stop test server', async () => {
  await stopTestServer(testServer);
});

export default tap.start();