const crypto = require('crypto');
const fs = require('fs');
const net = require('net');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');

const projectRoot = path.join(__dirname, '..');
const deploymentRoot = path.join(projectRoot, 'deploy', 'qbft-canonical');

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

function writeJson(filePath, value) {
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
}

function runPowerShell(scriptPath, args) {
  const result = spawnSync('powershell.exe', [
    '-NoLogo',
    '-NoProfile',
    '-NonInteractive',
    '-ExecutionPolicy', 'Bypass',
    '-File', scriptPath,
    ...args
  ], { encoding: 'utf8', windowsHide: true, maxBuffer: 4 * 1024 * 1024 });
  if (result.status !== 0) {
    const diagnostic = `${result.stdout || ''} ${result.stderr || ''}`.replace(/[\r\n]+/g, ' ').trim().slice(0, 1200);
    throw new Error(`Deployment tooling command failed (${path.basename(scriptPath)}): ${diagnostic}`);
  }
  return String(result.stdout || '').trim();
}

function findAssignedIpv4() {
  for (const addresses of Object.values(os.networkInterfaces())) {
    for (const entry of addresses || []) {
      if (entry.family === 'IPv4' && !entry.internal) return entry.address;
    }
  }
  return '127.0.0.1';
}

function reservePort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.once('error', reject);
    server.listen(0, '0.0.0.0', () => {
      const port = server.address().port;
      server.close((error) => error ? reject(error) : resolve(port));
    });
  });
}

function assertNoControlCharacters() {
  const scripts = fs.readdirSync(deploymentRoot).filter((name) => name.endsWith('.ps1'));
  for (const name of scripts) {
    const source = fs.readFileSync(path.join(deploymentRoot, name), 'utf8');
    for (let index = 0; index < source.length; index += 1) {
      const code = source.charCodeAt(index);
      if (code < 32 && ![9, 10, 13].includes(code)) {
        throw new Error(`${name} contains control character 0x${code.toString(16)} at offset ${index}.`);
      }
    }
  }
  return scripts.length;
}

function createFakeRuntime(runtimeRoot) {
  const besuLib = path.join(runtimeRoot, 'besu-26.6.1', 'lib');
  const jdkBin = path.join(runtimeRoot, 'jdk-25.0.3+9', 'bin');
  fs.mkdirSync(besuLib, { recursive: true });
  fs.mkdirSync(jdkBin, { recursive: true });
  fs.writeFileSync(path.join(besuLib, 'self-test.jar'), 'not-executable');
  fs.writeFileSync(path.join(jdkBin, 'java.exe'), 'not-executable');
}

function createValidatorPackage(packageRoot, advertisedHost, p2pPort) {
  fs.mkdirSync(packageRoot, { recursive: true });
  const genesisPath = path.join(packageRoot, 'genesis.json');
  fs.writeFileSync(genesisPath, '{"config":{"chainId":20260718}}\n');
  const config = {
    version: 1,
    mode: 'preserve-canonical-genesis',
    finalized: true,
    role: 'validator',
    nodeName: 'validator-tooling-self-test',
    pcName: 'tooling-self-test',
    owner: 'self-test',
    chainId: 20260718,
    genesisSha256: sha256File(genesisPath),
    validatorAddress: `0x${'11'.repeat(20)}`,
    publicKey: '22'.repeat(64),
    advertisedHost,
    p2pPort,
    allowedPeerHosts: [],
    activationRequired: true
  };
  writeJson(path.join(packageRoot, 'node-config.json'), config);
  writeJson(path.join(packageRoot, 'static-nodes.json'), []);
  fs.writeFileSync(path.join(packageRoot, 'key.pub'), `${config.publicKey}\n`);
  fs.writeFileSync(path.join(packageRoot, 'validator.key'), `${'33'.repeat(32)}\n`);
  for (const name of ['install-node.ps1', 'start-node.ps1', 'preflight-node.ps1', 'approve-node-activation.ps1']) {
    fs.copyFileSync(path.join(deploymentRoot, name), path.join(packageRoot, name));
  }
  const files = fs.readdirSync(packageRoot).sort().map((name) => ({ name, sha256: sha256File(path.join(packageRoot, name)) }));
  writeJson(path.join(packageRoot, 'manifest.json'), {
    version: 1,
    role: config.role,
    nodeName: config.nodeName,
    genesisSha256: config.genesisSha256,
    files,
    createdAt: new Date().toISOString()
  });
  return config;
}

async function main() {
  const scriptCount = assertNoControlCharacters();
  const port = await reservePort();
  const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'plai-qbft-tooling-'));
  const resolvedTempRoot = path.resolve(temporaryRoot);
  const expectedPrefix = `${path.resolve(os.tmpdir())}${path.sep}`;
  if (!resolvedTempRoot.startsWith(expectedPrefix)) throw new Error('Unsafe self-test temporary directory.');
  try {
    const runtimeRoot = path.join(temporaryRoot, 'runtime-source');
    const packageRoot = path.join(temporaryRoot, 'package');
    const installRoot = path.join(temporaryRoot, 'installed-node');
    createFakeRuntime(runtimeRoot);
    const config = createValidatorPackage(packageRoot, findAssignedIpv4(), port);

    runPowerShell(path.join(packageRoot, 'install-node.ps1'), [
      '-RuntimeSource', runtimeRoot,
      '-PackageDir', packageRoot,
      '-InstallRoot', installRoot,
      '-SkipFirewall',
      '-SkipStartupTask'
    ]);

    const requiredInstalledPaths = [
      path.join(installRoot, 'runtime', 'besu', 'lib'),
      path.join(installRoot, 'runtime', 'jdk', 'bin', 'java.exe'),
      path.join(installRoot, 'secrets', 'validator.key'),
      path.join(installRoot, 'start-node.ps1')
    ];
    if (!requiredInstalledPaths.every((filePath) => fs.existsSync(filePath))) {
      throw new Error('Installer did not create the expected canonical node layout.');
    }

    const preflightOutput = runPowerShell(path.join(installRoot, 'preflight-node.ps1'), ['-InstallRoot', installRoot]);
    const preflight = JSON.parse(preflightOutput);
    if (!preflight.ok || preflight.chainId !== config.chainId) throw new Error('Installed-node preflight failed.');

    runPowerShell(path.join(installRoot, 'approve-node-activation.ps1'), [
      '-InstallRoot', installRoot,
      '-ConfirmValidatorAddress', config.validatorAddress
    ]);
    if (!fs.existsSync(path.join(installRoot, 'activation-approved.json'))) {
      throw new Error('Validator activation approval was not created.');
    }

    console.log(JSON.stringify({
      ok: true,
      deploymentScriptsChecked: scriptCount,
      canonicalLayoutVerified: true,
      validatorKeyAclVerified: true,
      preflightVerified: true,
      activationGateVerified: true,
      validatorStarted: false
    }, null, 2));
  } finally {
    fs.rmSync(resolvedTempRoot, { recursive: true, force: true });
  }
}

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