import { tap, expect } from '@git.zone/tstest/tapbundle';
import { IPReputationChecker, ReputationThreshold, IPType } from '../ts/security/classes.ipreputationchecker.js';
import * as plugins from '../ts/plugins.js';

// Mock for dns lookup
const originalDnsResolve = plugins.dns.promises.resolve;
let mockDnsResolveImpl: (hostname: string) => Promise<string[]> = async () => ['127.0.0.1'];

// Setup mock DNS resolver with proper typing
(plugins.dns.promises as any).resolve = async (hostname: string) => {
  return mockDnsResolveImpl(hostname);
};

// Test instantiation
tap.test('IPReputationChecker - should be instantiable', async () => {
  const checker = IPReputationChecker.getInstance({
    enableDNSBL: false,
    enableIPInfo: false,
    enableLocalCache: false
  });
  
  expect(checker).toBeTruthy();
});

// Test singleton pattern
tap.test('IPReputationChecker - should use singleton pattern', async () => {
  const checker1 = IPReputationChecker.getInstance();
  const checker2 = IPReputationChecker.getInstance();
  
  // Both instances should be the same object
  expect(checker1 === checker2).toEqual(true);
});

// Test IP validation
tap.test('IPReputationChecker - should validate IP address format', async () => {
  const checker = IPReputationChecker.getInstance({
    enableDNSBL: false,
    enableIPInfo: false,
    enableLocalCache: false
  });
  
  // Valid IP should work
  const result = await checker.checkReputation('192.168.1.1');
  expect(result.score).toBeGreaterThan(0);
  expect(result.error).toBeUndefined();
  
  // Invalid IP should fail with error
  const invalidResult = await checker.checkReputation('invalid.ip');
  expect(invalidResult.error).toBeTruthy();
});

// Test DNSBL lookups
tap.test('IPReputationChecker - should check IP against DNSBL', async () => {
  try {
    // Setup mock implementation for DNSBL
    mockDnsResolveImpl = async (hostname: string) => {
      // Listed in DNSBL if IP contains 2
      if (hostname.includes('2.1.168.192') && hostname.includes('zen.spamhaus.org')) {
        return ['127.0.0.2'];
      }
      throw { code: 'ENOTFOUND' };
    };

    // Create a new instance with specific settings for this test
    const testInstance = new IPReputationChecker({
      dnsblServers: ['zen.spamhaus.org'],
      enableIPInfo: false,
      enableLocalCache: false,
      maxCacheSize: 1 // Small cache for testing
    });
    
    // Clean IP should have good score
    const cleanResult = await testInstance.checkReputation('192.168.1.1');
    expect(cleanResult.isSpam).toEqual(false);
    expect(cleanResult.score).toEqual(100);
    
    // Blacklisted IP should have reduced score
    const blacklistedResult = await testInstance.checkReputation('192.168.1.2');
    expect(blacklistedResult.isSpam).toEqual(true);
    expect(blacklistedResult.score < 100).toEqual(true); // Less than 100
    expect(blacklistedResult.blacklists).toBeTruthy();
    expect((blacklistedResult.blacklists || []).length > 0).toEqual(true);
  } catch (err) {
    console.error('Test error:', err);
    throw err;
  }
});

// Test caching behavior
tap.test('IPReputationChecker - should cache reputation results', async () => {
  // Create a fresh instance for this test
  const testInstance = new IPReputationChecker({
    enableIPInfo: false,
    enableLocalCache: false,
    maxCacheSize: 10 // Small cache for testing
  });
  
  // Check that first look performs a lookup and second uses cache
  const ip = '192.168.1.10';
  
  // First check should add to cache
  const result1 = await testInstance.checkReputation(ip);
  expect(result1).toBeTruthy();
  
  // Manually verify it's in cache - access private member for testing
  const hasInCache = (testInstance as any).reputationCache.has(ip);
  expect(hasInCache).toEqual(true);
  
  // Call again, should use cache
  const result2 = await testInstance.checkReputation(ip);
  expect(result2).toBeTruthy();
  
  // Results should be identical
  expect(result1.score).toEqual(result2.score);
});

// Test risk level classification
tap.test('IPReputationChecker - should classify risk levels correctly', async () => {
  expect(IPReputationChecker.getRiskLevel(10)).toEqual('high');
  expect(IPReputationChecker.getRiskLevel(30)).toEqual('medium');
  expect(IPReputationChecker.getRiskLevel(60)).toEqual('low');
  expect(IPReputationChecker.getRiskLevel(90)).toEqual('trusted');
});

// Test IP type detection
tap.test('IPReputationChecker - should detect special IP types', async () => {
  const testInstance = new IPReputationChecker({
    enableDNSBL: false,
    enableIPInfo: true,
    enableLocalCache: false,
    maxCacheSize: 5 // Small cache for testing
  });
  
  // Test Tor exit node detection
  const torResult = await testInstance.checkReputation('171.25.1.1');
  expect(torResult.isTor).toEqual(true);
  expect(torResult.score < 90).toEqual(true);
  
  // Test VPN detection
  const vpnResult = await testInstance.checkReputation('185.156.1.1');
  expect(vpnResult.isVPN).toEqual(true);
  expect(vpnResult.score < 90).toEqual(true);
  
  // Test proxy detection
  const proxyResult = await testInstance.checkReputation('34.92.1.1');
  expect(proxyResult.isProxy).toEqual(true);
  expect(proxyResult.score < 90).toEqual(true);
});

// Test error handling
tap.test('IPReputationChecker - should handle DNS lookup errors gracefully', async () => {
  // Setup mock implementation to simulate error
  mockDnsResolveImpl = async () => {
    throw new Error('DNS server error');
  };

  const checker = IPReputationChecker.getInstance({
    dnsblServers: ['zen.spamhaus.org'],
    enableIPInfo: false,
    enableLocalCache: false,
    maxCacheSize: 300 // Force new instance
  });
  
  // Should return a result despite errors
  const result = await checker.checkReputation('192.168.1.1');
  expect(result.score).toEqual(100); // No blacklist hits found due to error
  expect(result.isSpam).toEqual(false);
});

// Restore original implementation at the end
tap.test('Cleanup - restore mocks', async () => {
  plugins.dns.promises.resolve = originalDnsResolve;
});

tap.test('stop', async () => {
  await tap.stopForcefully();
});

export default tap.start();