require('../config/loadEnv')();
const fs = require('fs');
const crypto = require('crypto');
const net = require('net');
const path = require('path');
const { spawn } = require('child_process');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');

const projectRoot = path.join(__dirname, '..');
const runtimeRoot = path.join(projectRoot, 'runtime');
const javaHome = path.join(runtimeRoot, 'jdk-25.0.3+9');
const besuRoot = path.join(runtimeRoot, 'besu-26.6.1');
const javaCommand = path.join(javaHome, 'bin', 'java.exe');
const besuClasspath = path.join(besuRoot, 'lib', '*');
const networkRoot = path.join(runtimeRoot, 'plais-qbft');
const networkPath = path.join(networkRoot, 'network.json');
const nodeName = String(process.argv[2] || '').trim();

if (!EVM_CANDIDATE_CONFIG.enabled) {
  console.error('PLAIS EVM candidate is disabled.');
  process.exit(1);
}
if (!fs.existsSync(networkPath)) {
  console.error('QBFT network is not initialized. Run npm run evm:setup-qbft first.');
  process.exit(1);
}

const network = JSON.parse(fs.readFileSync(networkPath, 'utf8'));
const node = network.nodes.find((entry) => entry.name === nodeName);
if (!node) {
  console.error(`Unknown QBFT node: ${nodeName}`);
  process.exit(1);
}

function sha256File(filePath) {
  return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
}

function readBridgeNode() {
  const configuredPath = String(process.env.PLAIS_QBFT_BRIDGE_CONFIG || '').trim();
  if (!configuredPath) return null;
  const bridgePath = path.isAbsolute(configuredPath) ? configuredPath : path.join(projectRoot, configuredPath);
  if (!fs.existsSync(bridgePath)) throw new Error(`QBFT bridge config is missing: ${bridgePath}`);
  const bridge = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
  const bridgeNode = (bridge.nodes || []).find((entry) => entry.name === nodeName);
  if (bridge.state !== 'READY_FOR_BRIDGE' || bridge.mode !== 'preserve-canonical-genesis' || !bridgeNode) {
    throw new Error('QBFT bridge config is not finalized for this canonical node.');
  }
  if (Number(bridge.chainId) !== Number(network.chainId)
    || bridge.genesisSha256 !== sha256File(network.genesisFile)) {
    throw new Error('QBFT bridge config does not match the canonical genesis.');
  }
  if (!net.isIP(bridge.advertisedHost) || bridge.advertisedHost === '127.0.0.1' || bridge.advertisedHost === '0.0.0.0') {
    throw new Error('QBFT bridge advertisedHost must be a non-loopback IP address.');
  }
  return { ...bridgeNode, advertisedHost: bridge.advertisedHost, interface: bridge.interface || '0.0.0.0' };
}

function readLocalPublicBridgeStaticNode() {
  const publicBridgeRoot = path.join(runtimeRoot, 'plais-qbft-public-bridge');
  const configPath = path.join(publicBridgeRoot, 'config.json');
  const publicKeyPath = path.join(publicBridgeRoot, 'data', 'key.pub');
  if (!fs.existsSync(configPath) || !fs.existsSync(publicKeyPath)) return '';
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  const advertisedHost = String(config.advertisedHost || '').trim();
  const p2pPort = Number(config.p2pPort || 0);
  const publicKey = fs.readFileSync(publicKeyPath, 'utf8').trim().replace(/^0x/, '');
  if (config.schemaVersion !== 'plais-qbft-public-bridge/v1'
    || config.enabled !== true
    || net.isIP(advertisedHost) !== 4
    || !Number.isInteger(p2pPort)
    || p2pPort < 1024
    || p2pPort > 65535
    || !/^[a-f0-9]{128}$/i.test(publicKey)) return '';
  return `enode://${publicKey}@${advertisedHost}:${p2pPort}`;
}

let bridgeNode = null;
try {
  bridgeNode = readBridgeNode();
} catch (error) {
  console.error(error.message);
  process.exit(1);
}

const nodeDataPath = path.join(networkRoot, node.name, 'data');
const args = [
  `--data-path=${nodeDataPath}`,
  `--genesis-file=${network.genesisFile}`,
  '--profile=ENTERPRISE',
  '--sync-mode=FULL',
  '--data-storage-format=BONSAI',
  `--p2p-host=${bridgeNode ? bridgeNode.advertisedHost : '127.0.0.1'}`,
  `--p2p-interface=${bridgeNode ? bridgeNode.interface : '127.0.0.1'}`,
  `--p2p-port=${node.p2pPort}`,
  '--discovery-enabled=false',
  '--nat-method=NONE',
  '--min-gas-price=0',
  '--logging=INFO'
];
if (bridgeNode || node.role === 'validator' || node.role === 'rpc') {
  const staticNodesPath = path.join(nodeDataPath, 'static-nodes.json');
  const staticNodes = bridgeNode
    ? bridgeNode.staticNodes
    : network.nodes
      .filter((entry) => entry.role === 'validator')
      .filter((entry) => node.role === 'rpc' || entry.name !== node.name)
      .map((entry) => {
        const publicKey = fs.readFileSync(
          path.join(networkRoot, entry.name, 'data', 'key.pub'),
          'utf8'
        ).trim().replace(/^0x/, '');
        return `enode://${publicKey}@127.0.0.1:${entry.p2pPort}`;
      });
  if (node.role === 'rpc') {
    if (!bridgeNode) {
      const publicBridgePeer = readLocalPublicBridgeStaticNode();
      if (publicBridgePeer && !staticNodes.includes(publicBridgePeer)) staticNodes.push(publicBridgePeer);
    }
  }
  fs.writeFileSync(staticNodesPath, `${JSON.stringify(staticNodes, null, 2)}\n`);
  args.push(`--static-nodes-file=${staticNodesPath}`);
}
if (node.role === 'rpc') {
  args.push(
    '--rpc-http-enabled=true',
    `--rpc-http-host=${EVM_CANDIDATE_CONFIG.rpcHost}`,
    `--rpc-http-port=${EVM_CANDIDATE_CONFIG.rpcPort}`,
    '--rpc-http-api=ETH,NET,WEB3,QBFT,ADMIN',
    '--host-allowlist=localhost,127.0.0.1',
    '--metrics-enabled=true',
    '--metrics-host=127.0.0.1',
    '--metrics-port=9545'
  );
}

console.log(`Starting PLAIS ${network.consensus} ${node.name} on P2P ${node.p2pPort}`);
const child = spawn(javaCommand, [
  '--enable-native-access=ALL-UNNAMED',
  '-Xms128m',
  '-Xmx512m',
  '-cp', besuClasspath,
  'org.hyperledger.besu.Besu',
  ...args
], {
  cwd: besuRoot,
  env: {
    ...process.env,
    JAVA_HOME: javaHome
  },
  stdio: 'inherit',
  shell: false,
  windowsHide: true
});

child.on('exit', (code, signal) => {
  console.log(`PLAIS ${node.name} stopped code=${code} signal=${signal || '-'}`);
  process.exit(code == null ? 1 : code);
});
child.on('error', (error) => {
  console.error(error);
  process.exit(1);
});

function shutdown(signal) {
  if (!child.killed) child.kill(signal);
}
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
