import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
import { NFTablesManager } from '../ts/proxies/smart-proxy/nftables-manager.js';
import { createNfTablesRoute } from '../ts/proxies/smart-proxy/utils/route-helpers.js';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as child_process from 'child_process';
import { promisify } from 'util';

const exec = promisify(child_process.exec);

// Check if we have root privileges
async function checkRootPrivileges(): Promise<boolean> {
  try {
    const { stdout } = await exec('id -u');
    return stdout.trim() === '0';
  } catch (err) {
    return false;
  }
}

// Skip tests if not root
const isRoot = await checkRootPrivileges();
if (!isRoot) {
  console.log('');
  console.log('========================================');
  console.log('NFTables status tests require root privileges');
  console.log('Skipping NFTables status tests');
  console.log('========================================');
  console.log('');
}

// Define the test function based on root privileges
const testFn = isRoot ? tap.test : tap.skip.test;

testFn('NFTablesManager status functionality', async () => {
  const nftablesManager = new NFTablesManager({ routes: [] });
  
  // Create test routes
  const testRoutes = [
    createNfTablesRoute('test-route-1', { host: 'localhost', port: 8080 }, { ports: 9080 }),
    createNfTablesRoute('test-route-2', { host: 'localhost', port: 8081 }, { ports: 9081 }),
    createNfTablesRoute('test-route-3', { host: 'localhost', port: 8082 }, { 
      ports: 9082,
      ipAllowList: ['127.0.0.1', '192.168.1.0/24']
    })
  ];
  
  // Get initial status (should be empty)
  let status = await nftablesManager.getStatus();
  expect(Object.keys(status).length).toEqual(0);
  
  // Provision routes
  for (const route of testRoutes) {
    await nftablesManager.provisionRoute(route);
  }
  
  // Get status after provisioning
  status = await nftablesManager.getStatus();
  expect(Object.keys(status).length).toEqual(3);
  
  // Check status structure
  for (const routeStatus of Object.values(status)) {
    expect(routeStatus).toHaveProperty('active');
    expect(routeStatus).toHaveProperty('ruleCount');
    expect(routeStatus).toHaveProperty('lastUpdate');
    expect(routeStatus.active).toBeTrue();
  }
  
  // Deprovision one route
  await nftablesManager.deprovisionRoute(testRoutes[0]);
  
  // Check status after deprovisioning
  status = await nftablesManager.getStatus();
  expect(Object.keys(status).length).toEqual(2);
  
  // Cleanup remaining routes
  await nftablesManager.stop();
  
  // Final status should be empty
  status = await nftablesManager.getStatus();
  expect(Object.keys(status).length).toEqual(0);
});

testFn('SmartProxy getNfTablesStatus functionality', async () => {
  const smartProxy = new SmartProxy({
    routes: [
      createNfTablesRoute('proxy-test-1', { host: 'localhost', port: 3000 }, { ports: 3001 }),
      createNfTablesRoute('proxy-test-2', { host: 'localhost', port: 3002 }, { ports: 3003 }),
      // Include a non-NFTables route to ensure it's not included in the status
      {
        name: 'non-nftables-route',
        match: { ports: 3004 },
        action: {
          type: 'forward',
          target: { host: 'localhost', port: 3005 }
        }
      }
    ]
  });
  
  // Start the proxy
  await smartProxy.start();
  
  // Get NFTables status
  const status = await smartProxy.getNfTablesStatus();
  
  // Should only have 2 NFTables routes
  const statusKeys = Object.keys(status);
  expect(statusKeys.length).toEqual(2);
  
  // Check that both NFTables routes are in the status
  const routeIds = statusKeys.sort();
  expect(routeIds).toContain('proxy-test-1:3001');
  expect(routeIds).toContain('proxy-test-2:3003');
  
  // Verify status structure
  for (const [routeId, routeStatus] of Object.entries(status)) {
    expect(routeStatus).toHaveProperty('active', true);
    expect(routeStatus).toHaveProperty('ruleCount');
    expect(routeStatus.ruleCount).toHaveProperty('total');
    expect(routeStatus.ruleCount.total).toBeGreaterThan(0);
  }
  
  // Stop the proxy
  await smartProxy.stop();
  
  // After stopping, status should be empty
  const finalStatus = await smartProxy.getNfTablesStatus();
  expect(Object.keys(finalStatus).length).toEqual(0);
});

testFn('NFTables route update status tracking', async () => {
  const smartProxy = new SmartProxy({
    routes: [
      createNfTablesRoute('update-test', { host: 'localhost', port: 4000 }, { ports: 4001 })
    ]
  });
  
  await smartProxy.start();
  
  // Get initial status
  let status = await smartProxy.getNfTablesStatus();
  expect(Object.keys(status).length).toEqual(1);
  const initialUpdate = status['update-test:4001'].lastUpdate;
  
  // Wait a moment
  await new Promise(resolve => setTimeout(resolve, 10));
  
  // Update the route
  await smartProxy.updateRoutes([
    createNfTablesRoute('update-test', { host: 'localhost', port: 4002 }, { ports: 4001 })
  ]);
  
  // Get status after update
  status = await smartProxy.getNfTablesStatus();
  expect(Object.keys(status).length).toEqual(1);
  const updatedTime = status['update-test:4001'].lastUpdate;
  
  // The update time should be different
  expect(updatedTime.getTime()).toBeGreaterThan(initialUpdate.getTime());
  
  await smartProxy.stop();
});

export default tap.start();