require('../config/loadEnv')();

const fs = require('fs');
const http = require('http');
const path = require('path');
const { spawn } = require('child_process');

const root = path.join(__dirname, '..');
const runtimeRoot = path.join(root, 'runtime');
const javaHome = path.join(runtimeRoot, 'jdk-25.0.3+9');
const javaPath = path.join(javaHome, 'bin', 'java.exe');
const besuRoot = path.join(runtimeRoot, 'besu-26.6.1');
const classPath = path.join(besuRoot, 'lib', '*');
const genesisPath = path.join(runtimeRoot, 'plais-qbft', 'generated', 'genesis.json');
const bridgeRoot = path.join(runtimeRoot, 'plais-qbft-public-bridge');
const bridgeConfig = JSON.parse(fs.readFileSync(path.join(bridgeRoot, 'config.json'), 'utf8'));
const bridgePublicKey = fs.readFileSync(path.join(bridgeRoot, 'data', 'key.pub'), 'utf8').trim().replace(/^0x/, '');
const resumeArg = process.argv.find(value => value.startsWith('--resume='));
const requestedResume = resumeArg ? path.resolve(resumeArg.slice('--resume='.length)) : '';
if (requestedResume && (path.dirname(requestedResume).toLowerCase() !== runtimeRoot.toLowerCase() || !path.basename(requestedResume).startsWith('plais-qbft-snap-smoke-'))) {
  throw new Error('SNAP smoke resume path is outside the isolated runtime root.');
}
const smokeRoot = requestedResume || path.join(runtimeRoot, `plais-qbft-snap-smoke-${Date.now()}`);
const dataPath = path.join(smokeRoot, 'data');
const staticNodesPath = path.join(dataPath, 'static-nodes.json');
const p2pPort = 31308;
const rpcPort = 18546;

if (!/^[a-f0-9]{128}$/i.test(bridgePublicKey)) throw new Error('Bridge public key is invalid.');
fs.mkdirSync(dataPath, { recursive: true });
fs.writeFileSync(staticNodesPath, JSON.stringify([
  `enode://${bridgePublicKey}@127.0.0.1:${Number(bridgeConfig.p2pPort)}`
], null, 2));

function rpc(method) {
  return new Promise((resolve, reject) => {
    const body = Buffer.from(JSON.stringify({ jsonrpc: '2.0', id: 1, method, params: [] }));
    const request = http.request({ host: '127.0.0.1', port: rpcPort, path: '/', method: 'POST', timeout: 2500, headers: { 'Content-Type': 'application/json', 'Content-Length': body.length } }, response => {
      const chunks = [];
      response.on('data', chunk => chunks.push(chunk));
      response.on('end', () => {
        try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')).result); } catch (error) { reject(error); }
      });
    });
    request.on('error', reject);
    request.on('timeout', () => request.destroy(new Error('timeout')));
    request.end(body);
  });
}

async function main() {
  const args = [
    '--enable-native-access=ALL-UNNAMED', '-Xms128m', '-Xmx512m', '-cp', classPath, 'org.hyperledger.besu.Besu',
    `--data-path=${dataPath}`, `--genesis-file=${genesisPath}`, '--profile=ENTERPRISE', '--sync-mode=SNAP',
    '--data-storage-format=BONSAI', '--p2p-host=127.0.0.1', '--p2p-interface=127.0.0.1', `--p2p-port=${p2pPort}`,
    '--max-peers=2', '--discovery-enabled=false', '--nat-method=NONE', '--min-gas-price=0',
    '--rpc-http-enabled=true', '--rpc-http-host=127.0.0.1', `--rpc-http-port=${rpcPort}`,
    '--rpc-http-api=ETH,NET,WEB3', '--host-allowlist=localhost,127.0.0.1',
    `--static-nodes-file=${staticNodesPath}`, '--logging=INFO'
  ];
  const child = spawn(javaPath, args, { cwd: besuRoot, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, JAVA_HOME: javaHome } });
  const diagnostics = [];
  const capture = chunk => {
    for (const line of String(chunk).split(/\r?\n/)) {
      if (/Snap|sync|peer|ERROR|Exception/i.test(line)) diagnostics.push(line.replace(/\x1b\[[0-9;]*m/g, '').slice(0, 500));
    }
    if (diagnostics.length > 80) diagnostics.splice(0, diagnostics.length - 80);
  };
  child.stdout.on('data', capture);
  child.stderr.on('data', capture);
  let exit = null;
  child.once('exit', (code, signal) => { exit = { code, signal }; });
  const deadline = Date.now() + 5 * 60_000;
  let observation = { blockNumber: 0, peerCount: 0, syncing: null };
  try {
    while (Date.now() < deadline && !exit) {
      try {
        const [blockHex, peerHex, syncing] = await Promise.all([rpc('eth_blockNumber'), rpc('net_peerCount'), rpc('eth_syncing')]);
        observation = {
          blockNumber: Number.parseInt(String(blockHex), 16) || 0,
          peerCount: Number.parseInt(String(peerHex), 16) || 0,
          syncing
        };
        if (observation.peerCount >= 1 && observation.blockNumber >= 500_000) break;
      } catch (_) {}
      await new Promise(resolve => setTimeout(resolve, 3000));
    }
  } finally {
    if (!exit) child.kill('SIGTERM');
  }
  const ok = !exit && observation.peerCount >= 1 && observation.blockNumber >= 500_000;
  console.log(JSON.stringify({ ok, mode: 'SNAP', observation, exit, smokeRoot, diagnostics: diagnostics.slice(-25) }, null, 2));
  if (!ok) process.exitCode = 1;
}

main().catch(error => { console.error(JSON.stringify({ ok: false, error: error.message })); process.exit(1); });
