require('../config/loadEnv')();
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const persistentStore = require('../services/persistentStore');
const { matchesValidatorTarget } = require('../services/validatorTargetBindingService');

const projectRoot = path.join(__dirname, '..');
const migrationRoot = path.join(projectRoot, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const genesisPath = path.join(projectRoot, 'runtime', 'plais-qbft', 'generated', 'genesis.json');
const runtimeManifestPath = path.join(projectRoot, 'runtime', 'plais-validator-runtime', 'manifest.json');
const bridgeConfigPath = path.join(projectRoot, 'runtime', 'plais-qbft-public-bridge', 'config.json');
const approvalPath = path.join(migrationRoot, 'validator-runtime-deployment-approval.json');
const expectedConfirmation = 'DEPLOY_VALIDATOR_RUNTIME_PRESYNC';

function readJson(filePath) {
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

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

function latestNodeForTarget(nodes, target) {
  return nodes
    .filter((node) => matchesValidatorTarget(node, target))
    .sort((left, right) => new Date(right.lastHeartbeatAt || 0) - new Date(left.lastHeartbeatAt || 0))[0] || null;
}

function restrictAcl(filePath) {
  if (process.platform !== 'win32') return;
  const result = require('child_process').spawnSync('icacls.exe', [filePath, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:(F)`, 'SYSTEM:(F)', 'Administrators:(F)'], {
    encoding: 'utf8', windowsHide: true, timeout: 10_000
  });
  if (result.status !== 0) throw new Error(`Approval ACL restriction failed: ${String(result.stderr || result.stdout || '').trim()}`);
}

function main() {
  const confirmation = String(process.argv[2] || process.env.PLAIS_VALIDATOR_RUNTIME_CONFIRM || '').trim();
  const nodeArg = process.argv.slice(3).find(value => String(value).startsWith('--node='));
  const targetNodeName = String(nodeArg || '').slice('--node='.length);
  if (confirmation !== expectedConfirmation) {
    throw new Error(`Explicit confirmation required: npm run evm:approve-validator-runtime -- ${expectedConfirmation} --node=validator-1`);
  }
  if (!/^validator-[1-4]$/.test(targetNodeName)) throw new Error('Exactly one validator runtime deployment target is required.');
  if (fs.existsSync(approvalPath)) throw new Error(`Existing approval must expire or be removed after audit: ${approvalPath}`);
  for (const required of [topologyPath, genesisPath, runtimeManifestPath, bridgeConfigPath]) {
    if (!fs.existsSync(required)) throw new Error(`Required deployment binding missing: ${required}`);
  }

  const topology = readJson(topologyPath);
  const runtime = readJson(runtimeManifestPath);
  const bridge = readJson(bridgeConfigPath);
  if (
    topology.mode !== 'preserve-canonical-genesis'
    || Number(topology.chainId) !== 20260718
    || topology.genesisSha256 !== sha256File(genesisPath)
    || !Array.isArray(topology.validators)
    || topology.validators.length !== 4
    || runtime.schemaVersion !== 'plais-validator-runtime-artifact/v1'
    || runtime.contents?.privateKeysIncluded !== false
    || runtime.contents?.chainConfigurationIncluded !== false
    || runtime.sha256 !== sha256File(path.join('E:\\aisfor\\public\\downloads', runtime.fileName))
    || bridge.schemaVersion !== 'plais-qbft-public-bridge/v1'
    || bridge.enabled !== true
  ) throw new Error('Validator runtime deployment bindings failed validation.');

  const nodes = persistentStore.readState().nodes || [];
  const now = Date.now();
  const deviceKeyIds = {};
  const targets = topology.validators.map((target) => {
    const node = latestNodeForTarget(nodes, target);
    const heartbeatAt = new Date(node?.lastHeartbeatAt || 0).getTime();
    const keyId = String(node?.metadata?.provisioningKeyId || '').toLowerCase();
    const registered = node?.metadata?.chainProvisioningRole === 'validator-candidate'
      && /^[a-f0-9]{64}$/.test(keyId)
      && /^1\.4\.(?:2[5-9]|[3-9][0-9]|107|109|110|111|112|113|114)(?:\.|-|$)/.test(String(node?.clientVersion || ''));
    const targetReady = registered
      && node?.status === 'active'
      && Number.isFinite(heartbeatAt)
      && now - heartbeatAt >= -60_000
      && now - heartbeatAt <= 5 * 60_000;
    if (!registered || (target.nodeName === targetNodeName && !targetReady)) {
      throw new Error(`Validator target is not deployment-ready: ${target.pcName}`);
    }
    deviceKeyIds[target.nodeName] = keyId;
    return { nodeName: target.nodeName, pcName: target.pcName, nodeId: node.nodeId, clientVersion: node.clientVersion };
  });
  if (new Set(Object.values(deviceKeyIds)).size !== 4) throw new Error('Validator device encryption identities must be unique.');
  if (!targets.some(target => target.nodeName === targetNodeName)) throw new Error('Requested validator runtime target was not found.');

  const approvedAt = new Date();
  const approval = {
    schemaVersion: 'plais-validator-runtime-deployment-approval/v1',
    action: expectedConfirmation,
    approvalId: `runtime-presync-${approvedAt.toISOString().replace(/[^0-9]/g, '').slice(0, 14)}-${crypto.randomBytes(6).toString('hex')}`,
    approvedBy: process.env.USERNAME || 'local-operator',
    approvedAt: approvedAt.toISOString(),
    expiresAt: new Date(approvedAt.getTime() + 2 * 60 * 60 * 1000).toISOString(),
    topologySha256: sha256File(topologyPath),
    genesisSha256: sha256File(genesisPath),
    runtimeManifestSha256: sha256File(runtimeManifestPath),
    bridgeConfigSha256: sha256File(bridgeConfigPath),
    deviceKeyIds,
    targetNodeNames: [targetNodeName],
    controls: {
      runtimeDownloadAllowed: true,
      preSyncStartAllowed: true,
      validatorKeyDecryptAllowed: false,
      validatorKeyImportAllowed: false,
      validatorStartAllowed: false,
      arbitraryCommandAllowed: false
    }
  };
  fs.writeFileSync(approvalPath, JSON.stringify(approval, null, 2), { encoding: 'utf8', flag: 'wx' });
  restrictAcl(approvalPath);
  console.log(JSON.stringify({ ok: true, approvalId: approval.approvalId, expiresAt: approval.expiresAt, targetNodeName, controls: approval.controls }, null, 2));
}

try { main(); } catch (error) { console.error(error.message); process.exitCode = 1; }
