import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from './helpers/server.loader.js';
import { createTestSmtpClient } from './helpers/smtp.client.js';
import { SmtpClient } from '../ts/mail/delivery/smtpclient/smtp-client.js';

const TEST_PORT = 2525;

// Test email configuration with rate limits
const testEmailConfig = {
  ports: [TEST_PORT],
  hostname: 'localhost',
  domains: [
    {
      domain: 'test.local',
      dnsMode: 'forward' as const,
      rateLimits: {
        inbound: {
          messagesPerMinute: 3, // Very low limit for testing
          recipientsPerMessage: 2,
          connectionsPerIp: 5
        }
      }
    }
  ],
  routes: [
    {
      name: 'test-route',
      match: { recipients: '*@test.local' },
      action: { type: 'process' as const, process: { scan: false, queue: 'normal' } }
    }
  ],
  rateLimits: {
    global: {
      maxMessagesPerMinute: 10,
      maxConnectionsPerIP: 10,
      maxErrorsPerIP: 3,
      maxAuthFailuresPerIP: 2,
      blockDuration: 5000 // 5 seconds for testing
    }
  }
};

tap.test('prepare server with rate limiting', async () => {
  await plugins.startTestServer(testEmailConfig);
  // Give server time to start
  await new Promise(resolve => setTimeout(resolve, 1000));
});

tap.test('should enforce connection rate limits', async (tools) => {
  const done = tools.defer();
  const clients: SmtpClient[] = [];
  
  try {
    // Try to create many connections quickly
    for (let i = 0; i < 12; i++) {
      const client = createTestSmtpClient();
      clients.push(client);
      
      // Connection should fail after limit is exceeded
      const verified = await client.verify().catch(() => false);
      
      if (i < 10) {
        // First 10 should succeed (global limit)
        expect(verified).toBeTrue();
      } else {
        // After 10, should be rate limited
        expect(verified).toBeFalse();
      }
    }
    
    done.resolve();
  } catch (error) {
    done.reject(error);
  } finally {
    // Clean up connections
    for (const client of clients) {
      await client.close().catch(() => {});
    }
  }
});

tap.test('should enforce message rate limits per domain', async (tools) => {
  const done = tools.defer();
  const client = createTestSmtpClient();
  
  try {
    // Send messages rapidly to test domain-specific rate limit
    for (let i = 0; i < 5; i++) {
      const email = {
        from: `sender${i}@example.com`,
        to: 'recipient@test.local',
        subject: `Test ${i}`,
        text: 'Test message'
      };
      
      const result = await client.sendMail(email).catch(err => err);
      
      if (i < 3) {
        // First 3 should succeed (domain limit is 3 per minute)
        expect(result.accepted).toBeDefined();
        expect(result.accepted.length).toEqual(1);
      } else {
        // After 3, should be rate limited
        expect(result.code).toEqual('EENVELOPE');
        expect(result.response).toContain('try again later');
      }
    }
    
    done.resolve();
  } catch (error) {
    done.reject(error);
  } finally {
    await client.close();
  }
});

tap.test('should enforce recipient limits', async (tools) => {
  const done = tools.defer();
  const client = createTestSmtpClient();
  
  try {
    // Try to send to many recipients (domain limit is 2 per message)
    const email = {
      from: 'sender@example.com',
      to: ['user1@test.local', 'user2@test.local', 'user3@test.local'],
      subject: 'Test with multiple recipients',
      text: 'Test message'
    };
    
    const result = await client.sendMail(email).catch(err => err);
    
    // Should fail due to recipient limit
    expect(result.code).toEqual('EENVELOPE');
    expect(result.response).toContain('try again later');
    
    done.resolve();
  } catch (error) {
    done.reject(error);
  } finally {
    await client.close();
  }
});

tap.test('should enforce error rate limits', async (tools) => {
  const done = tools.defer();
  const client = createTestSmtpClient();
  
  try {
    // Send multiple invalid commands to trigger error rate limit
    const socket = (client as any).socket;
    
    // Wait for connection
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Send invalid commands
    for (let i = 0; i < 5; i++) {
      socket.write('INVALID_COMMAND\r\n');
      
      // Wait for response
      await new Promise(resolve => {
        socket.once('data', resolve);
      });
    }
    
    // After 3 errors, connection should be blocked
    const lastResponse = await new Promise<string>(resolve => {
      socket.once('data', (data: Buffer) => resolve(data.toString()));
      socket.write('NOOP\r\n');
    });
    
    expect(lastResponse).toContain('421 Too many errors');
    
    done.resolve();
  } catch (error) {
    done.reject(error);
  } finally {
    await client.close().catch(() => {});
  }
});

tap.test('should enforce authentication failure limits', async (tools) => {
  const done = tools.defer();
  
  // Create config with auth required
  const authConfig = {
    ...testEmailConfig,
    auth: {
      required: true,
      methods: ['PLAIN' as const]
    }
  };
  
  // Restart server with auth config
  await plugins.stopTestServer();
  await plugins.startTestServer(authConfig);
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  const client = createTestSmtpClient();
  
  try {
    // Try multiple failed authentications
    for (let i = 0; i < 3; i++) {
      const result = await client.sendMail({
        from: 'sender@example.com',
        to: 'recipient@test.local',
        subject: 'Test',
        text: 'Test'
      }, {
        auth: {
          user: 'wronguser',
          pass: 'wrongpass'
        }
      }).catch(err => err);
      
      if (i < 2) {
        // First 2 should fail with auth error
        expect(result.code).toEqual('EAUTH');
      } else {
        // After 2 failures, should be blocked
        expect(result.code).toEqual('ECONNECTION');
      }
    }
    
    done.resolve();
  } catch (error) {
    done.reject(error);
  } finally {
    await client.close().catch(() => {});
  }
});

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

tap.start();