import { expect, tap } from '@push.rocks/tapbundle';
import * as tsclass from '@tsclass/tsclass';
import * as http from 'http';
import { ProxyRouter, type IRouterResult } from '../ts/classes.router.js';

// Test proxies and configurations
let router: ProxyRouter;

// Sample hostname for testing
const TEST_DOMAIN = 'example.com';
const TEST_SUBDOMAIN = 'api.example.com';
const TEST_WILDCARD = '*.example.com';

// Helper: Creates a mock HTTP request for testing
function createMockRequest(host: string, url: string = '/'): http.IncomingMessage {
  const req = {
    headers: { host },
    url,
    socket: {
      remoteAddress: '127.0.0.1'
    }
  } as any;
  return req;
}

// Helper: Creates a test proxy configuration
function createProxyConfig(
  hostname: string, 
  destinationIp: string = '10.0.0.1',
  destinationPort: number = 8080
): tsclass.network.IReverseProxyConfig {
  return {
    hostName: hostname,
    destinationIp,
    destinationPort: destinationPort.toString(), // Convert to string for IReverseProxyConfig
    publicKey: 'mock-cert',
    privateKey: 'mock-key'
  } as tsclass.network.IReverseProxyConfig;
}

// SETUP: Create a ProxyRouter instance
tap.test('setup proxy router test environment', async () => {
  router = new ProxyRouter();
  
  // Initialize with empty config
  router.setNewProxyConfigs([]);
});

// Test basic routing by hostname
tap.test('should route requests by hostname', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  const req = createMockRequest(TEST_DOMAIN);
  const result = router.routeReq(req);
  
  expect(result).toBeTruthy();
  expect(result).toEqual(config);
});

// Test handling of hostname with port number
tap.test('should handle hostname with port number', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  const req = createMockRequest(`${TEST_DOMAIN}:443`);
  const result = router.routeReq(req);
  
  expect(result).toBeTruthy();
  expect(result).toEqual(config);
});

// Test case-insensitive hostname matching
tap.test('should perform case-insensitive hostname matching', async () => {
  const config = createProxyConfig(TEST_DOMAIN.toLowerCase());
  router.setNewProxyConfigs([config]);
  
  const req = createMockRequest(TEST_DOMAIN.toUpperCase());
  const result = router.routeReq(req);
  
  expect(result).toBeTruthy();
  expect(result).toEqual(config);
});

// Test handling of unmatched hostnames
tap.test('should return undefined for unmatched hostnames', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  const req = createMockRequest('unknown.domain.com');
  const result = router.routeReq(req);
  
  expect(result).toBeUndefined();
});

// Test adding path patterns
tap.test('should match requests using path patterns', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  // Add a path pattern to the config
  router.setPathPattern(config, '/api/users');
  
  // Test that path matches
  const req1 = createMockRequest(TEST_DOMAIN, '/api/users');
  const result1 = router.routeReqWithDetails(req1);
  
  expect(result1).toBeTruthy();
  expect(result1.config).toEqual(config);
  expect(result1.pathMatch).toEqual('/api/users');
  
  // Test that non-matching path doesn't match
  const req2 = createMockRequest(TEST_DOMAIN, '/web/users');
  const result2 = router.routeReqWithDetails(req2);
  
  expect(result2).toBeUndefined();
});

// Test handling wildcard patterns
tap.test('should support wildcard path patterns', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  router.setPathPattern(config, '/api/*');
  
  // Test with path that matches the wildcard pattern
  const req = createMockRequest(TEST_DOMAIN, '/api/users/123');
  const result = router.routeReqWithDetails(req);
  
  expect(result).toBeTruthy();
  expect(result.config).toEqual(config);
  expect(result.pathMatch).toEqual('/api');
  
  // Print the actual value to diagnose issues
  console.log('Path remainder value:', result.pathRemainder);
  expect(result.pathRemainder).toBeTruthy();
  expect(result.pathRemainder).toEqual('/users/123');
});

// Test extracting path parameters
tap.test('should extract path parameters from URL', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  router.setPathPattern(config, '/users/:id/profile');
  
  const req = createMockRequest(TEST_DOMAIN, '/users/123/profile');
  const result = router.routeReqWithDetails(req);
  
  expect(result).toBeTruthy();
  expect(result.config).toEqual(config);
  expect(result.pathParams).toBeTruthy();
  expect(result.pathParams.id).toEqual('123');
});

// Test multiple configs for same hostname with different paths
tap.test('should support multiple configs for same hostname with different paths', async () => {
  const apiConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.1', 8001);
  const webConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.2', 8002);
  
  // Add both configs
  router.setNewProxyConfigs([apiConfig, webConfig]);
  
  // Set different path patterns
  router.setPathPattern(apiConfig, '/api');
  router.setPathPattern(webConfig, '/web');
  
  // Test API path routes to API config
  const apiReq = createMockRequest(TEST_DOMAIN, '/api/users');
  const apiResult = router.routeReq(apiReq);
  
  expect(apiResult).toEqual(apiConfig);
  
  // Test web path routes to web config
  const webReq = createMockRequest(TEST_DOMAIN, '/web/dashboard');
  const webResult = router.routeReq(webReq);
  
  expect(webResult).toEqual(webConfig);
  
  // Test unknown path returns undefined
  const unknownReq = createMockRequest(TEST_DOMAIN, '/unknown');
  const unknownResult = router.routeReq(unknownReq);
  
  expect(unknownResult).toBeUndefined();
});

// Test wildcard subdomains
tap.test('should match wildcard subdomains', async () => {
  const wildcardConfig = createProxyConfig(TEST_WILDCARD);
  router.setNewProxyConfigs([wildcardConfig]);
  
  // Test that subdomain.example.com matches *.example.com
  const req = createMockRequest('subdomain.example.com');
  const result = router.routeReq(req);
  
  expect(result).toBeTruthy();
  expect(result).toEqual(wildcardConfig);
});

// Test default configuration fallback
tap.test('should fall back to default configuration', async () => {
  const defaultConfig = createProxyConfig('*');
  const specificConfig = createProxyConfig(TEST_DOMAIN);
  
  router.setNewProxyConfigs([defaultConfig, specificConfig]);
  
  // Test specific domain routes to specific config
  const specificReq = createMockRequest(TEST_DOMAIN);
  const specificResult = router.routeReq(specificReq);
  
  expect(specificResult).toEqual(specificConfig);
  
  // Test unknown domain falls back to default config
  const unknownReq = createMockRequest('unknown.com');
  const unknownResult = router.routeReq(unknownReq);
  
  expect(unknownResult).toEqual(defaultConfig);
});

// Test priority between exact and wildcard matches
tap.test('should prioritize exact hostname over wildcard', async () => {
  const wildcardConfig = createProxyConfig(TEST_WILDCARD);
  const exactConfig = createProxyConfig(TEST_SUBDOMAIN);
  
  router.setNewProxyConfigs([wildcardConfig, exactConfig]);
  
  // Test that exact match takes priority
  const req = createMockRequest(TEST_SUBDOMAIN);
  const result = router.routeReq(req);
  
  expect(result).toEqual(exactConfig);
});

// Test adding and removing configurations
tap.test('should manage configurations correctly', async () => {
  router.setNewProxyConfigs([]);
  
  // Add a config
  const config = createProxyConfig(TEST_DOMAIN);
  router.addProxyConfig(config);
  
  // Verify routing works
  const req = createMockRequest(TEST_DOMAIN);
  let result = router.routeReq(req);
  
  expect(result).toEqual(config);
  
  // Remove the config and verify it no longer routes
  const removed = router.removeProxyConfig(TEST_DOMAIN);
  expect(removed).toBeTrue();
  
  result = router.routeReq(req);
  expect(result).toBeUndefined();
});

// Test path pattern specificity
tap.test('should prioritize more specific path patterns', async () => {
  const genericConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.1', 8001);
  const specificConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.2', 8002);
  
  router.setNewProxyConfigs([genericConfig, specificConfig]);
  
  router.setPathPattern(genericConfig, '/api/*');
  router.setPathPattern(specificConfig, '/api/users');
  
  // The more specific '/api/users' should match before the '/api/*' wildcard
  const req = createMockRequest(TEST_DOMAIN, '/api/users');
  const result = router.routeReq(req);
  
  expect(result).toEqual(specificConfig);
});

// Test getHostnames method
tap.test('should retrieve all configured hostnames', async () => {
  router.setNewProxyConfigs([
    createProxyConfig(TEST_DOMAIN),
    createProxyConfig(TEST_SUBDOMAIN)
  ]);
  
  const hostnames = router.getHostnames();
  
  expect(hostnames.length).toEqual(2);
  expect(hostnames).toContain(TEST_DOMAIN.toLowerCase());
  expect(hostnames).toContain(TEST_SUBDOMAIN.toLowerCase());
});

// Test handling missing host header
tap.test('should handle missing host header', async () => {
  const defaultConfig = createProxyConfig('*');
  router.setNewProxyConfigs([defaultConfig]);
  
  const req = createMockRequest('');
  req.headers.host = undefined;
  
  const result = router.routeReq(req);
  
  expect(result).toEqual(defaultConfig);
});

// Test complex path parameters
tap.test('should handle complex path parameters', async () => {
  const config = createProxyConfig(TEST_DOMAIN);
  router.setNewProxyConfigs([config]);
  
  router.setPathPattern(config, '/api/:version/users/:userId/posts/:postId');
  
  const req = createMockRequest(TEST_DOMAIN, '/api/v1/users/123/posts/456');
  const result = router.routeReqWithDetails(req);
  
  expect(result).toBeTruthy();
  expect(result.config).toEqual(config);
  expect(result.pathParams).toBeTruthy();
  expect(result.pathParams.version).toEqual('v1');
  expect(result.pathParams.userId).toEqual('123');
  expect(result.pathParams.postId).toEqual('456');
});

// Performance test
tap.test('should handle many configurations efficiently', async () => {
  const configs = [];
  
  // Create many configs with different hostnames
  for (let i = 0; i < 100; i++) {
    configs.push(createProxyConfig(`host-${i}.example.com`));
  }
  
  router.setNewProxyConfigs(configs);
  
  // Test middle of the list to avoid best/worst case
  const req = createMockRequest('host-50.example.com');
  const result = router.routeReq(req);
  
  expect(result).toEqual(configs[50]);
});

// Test cleanup
tap.test('cleanup proxy router test environment', async () => {
  // Clear all configurations
  router.setNewProxyConfigs([]);
  
  // Verify empty state
  expect(router.getHostnames().length).toEqual(0);
  expect(router.getProxyConfigs().length).toEqual(0);
});

export default tap.start();