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 { auditAcl, restrictAcl } = require('../lib/operatorDpapi');

const projectRoot = path.join(__dirname, '..');
const migrationRoot = path.join(projectRoot, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const manifestPath = path.join(migrationRoot, 'encrypted-key-envelopes', 'manifest.json');
const approvalPath = path.join(migrationRoot, 'encrypted-key-delivery-approval.json');
const supportedClientVersions = new Set([
  '1.4.30-validator-tunnel-approval-retry-single-install-locked',
  '1.4.31-validator-envelope-identity-rotation-single-install-locked',
  '1.4.32-validator-bridge-fallback-single-install-locked',
  '1.4.33-validator-presync-bridge-recovery-single-install-locked',
  '1.4.34-validator-runtime-diagnostic-single-install-locked',
  '1.4.35-validator-runtime-worker-guard-single-install-locked',
  '1.4.36-validator-memory-safe-single-install-locked',
  '1.4.37-validator-rpc-ready-recovery-single-install-locked',
  '1.4.38-validator-startup-monitor-single-install-locked',
  '1.4.39-validator-error-retention-single-install-locked',
  '1.4.40-validator-exit-code-diagnostic-single-install-locked',
  '1.4.41-validator-complete-exit-diagnostic-single-install-locked',
  '1.4.42-validator-dll-basename-diagnostic-single-install-locked',
  '1.4.43-validator-app-local-vc-runtime-single-install-locked',
  '1.4.44-validator-snap-presync-single-install-locked',
  '1.4.45-validator-activation-diagnostic-single-install-locked',
  '1.4.46-validator-failure-retention-single-install-locked',
  '1.4.47-validator-worker-lock-identity-single-install-locked',
  '1.4.48-validator-runtime-pid-identity-single-install-locked',
  '1.4.49-validator-tunnel-state-preservation-single-install-locked',
  '1.4.50-validator-worker-race-single-install-locked',
  '1.4.51-validator-orphan-worker-cleanup-single-install-locked',
  '1.4.52-validator-existing-besu-adoption-single-install-locked',
  '1.4.53-validator-static-topology-reconcile-single-install-locked',
  '1.4.54-validator-preserved-full-bridge-recovery-single-install-locked',
  '1.4.55-validator-tunnel-approval-refresh-single-install-locked',
  '1.4.56-validator-directed-tunnel-topology-single-install-locked',
  '1.4.57-validator-loopback-tunnel-peer-diagnostic-single-install-locked',
  '1.4.58-validator-launch-config-reconcile-single-install-locked',
  '1.4.59-validator-p2p-disconnect-diagnostic-single-install-locked',
  '1.4.60-validator-unique-advertised-endpoint-single-install-locked',
  '1.4.61-validator-active-data-path-reconcile-single-install-locked',
  '1.4.62-validator-approved-consensus-restart-single-install-locked',
  '1.4.63-validator-qbft-consensus-diagnostic-single-install-locked',
  '1.4.107-quick-tunnel-discovery-default-off-locked',
  '1.4.109-runtime-heartbeat-null-stale-fallback-default-off-locked',
  '1.4.110-data-replica-readiness-attestation-default-off-locked',
  '1.4.111-validator-node-mode-recovery-single-install-locked'
]);
const confirmPhrase = 'DELIVER_ENCRYPTED_VALIDATOR_KEY_ENVELOPES';

function fail(code, message) {
  throw Object.assign(new Error(message), { code });
}

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

function parseArguments() {
  const values = {};
  for (const arg of process.argv.slice(2)) {
    const match = String(arg).match(/^--([a-z-]+)=(.+)$/i);
    if (match) values[match[1]] = match[2];
  }
  return values;
}

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 main() {
  const args = parseArguments();
  if (args.confirm !== confirmPhrase) fail('KEY_DELIVERY_CONFIRMATION_REQUIRED', 'Exact encrypted key delivery confirmation is required.');
  if (!/^[A-Za-z0-9._:-]{8,128}$/.test(String(args['approved-by'] || ''))) {
    fail('KEY_DELIVERY_APPROVER_INVALID', 'A valid approved-by ceremony identifier is required.');
  }
  if (fs.existsSync(approvalPath)) fail('KEY_DELIVERY_APPROVAL_EXISTS', 'A key delivery approval already exists.');
  if (!fs.existsSync(topologyPath) || !fs.existsSync(manifestPath)) {
    fail('KEY_DELIVERY_ARTIFACTS_MISSING', 'Canonical topology or encrypted envelope manifest is missing.');
  }

  const topology = JSON.parse(fs.readFileSync(topologyPath, 'utf8'));
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
  if (topology.mode !== 'preserve-canonical-genesis' || !Array.isArray(topology.validators) || topology.validators.length !== 4) {
    fail('KEY_DELIVERY_TOPOLOGY_INVALID', 'Canonical four-validator topology is invalid.');
  }
  if (
    manifest.schemaVersion !== 'plais-encrypted-validator-key-delivery/v1'
    || manifest.plaintextKeyFilesWritten !== false
    || manifest.aclProtected !== true
    || manifest.genesisSha256 !== topology.genesisSha256
    || !Array.isArray(manifest.nodes)
    || manifest.nodes.length !== 4
    || !auditAcl(path.dirname(manifestPath)).ok
    || !auditAcl(manifestPath).ok
  ) {
    fail('KEY_DELIVERY_MANIFEST_INVALID', 'Encrypted validator envelope manifest or ACL is invalid.');
  }

  const nodes = persistentStore.readState().nodes || [];
  const now = Date.now();
  const deviceKeyIds = {};
  for (const target of topology.validators) {
    const node = latestNodeForTarget(nodes, target);
    const row = manifest.nodes.find((entry) => entry.nodeName === target.nodeName && entry.pcName === target.pcName);
    const heartbeatAgeMs = node?.lastHeartbeatAt ? now - new Date(node.lastHeartbeatAt).getTime() : Infinity;
    const deviceKeyId = String(node?.metadata?.provisioningKeyId || '');
    if (
      !node
      || node.status !== 'active'
      || !Number.isFinite(heartbeatAgeMs)
      || heartbeatAgeMs > 5 * 60 * 1000
      || !supportedClientVersions.has(String(node.clientVersion || ''))
      || node.metadata?.chainProvisioningRole !== 'validator-candidate'
      || node.metadata?.chainProvisioningState !== 'awaiting-operator-approval'
      || !row
      || !/^[a-f0-9]{64}$/.test(deviceKeyId)
      || row.deviceKeyId !== deviceKeyId
    ) {
      fail('KEY_DELIVERY_TARGET_NOT_READY', `Validator delivery target is not ready: ${target.pcName}`);
    }
    deviceKeyIds[target.nodeName] = deviceKeyId;
  }

  const approvedAt = new Date();
  const approval = {
    action: confirmPhrase,
    approvalId: `validator-key-delivery-${approvedAt.getTime()}-${crypto.randomBytes(6).toString('hex')}`,
    approvedBy: args['approved-by'],
    approvedAt: approvedAt.toISOString(),
    expiresAt: new Date(approvedAt.getTime() + 10 * 60 * 1000).toISOString(),
    topologySha256: sha256File(topologyPath),
    manifestSha256: sha256File(manifestPath),
    genesisSha256: topology.genesisSha256,
    deviceKeyIds,
    controls: {
      encryptedEnvelopeDownloadAllowed: true,
      validatorKeyDecryptAllowed: false,
      validatorKeyImportAllowed: false,
      validatorStartAllowed: false,
      arbitraryCommandAllowed: false
    }
  };
  const tempPath = `${approvalPath}.tmp-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
  fs.writeFileSync(tempPath, `${JSON.stringify(approval, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
  if (!restrictAcl(tempPath, false).ok) fail('KEY_DELIVERY_APPROVAL_ACL_FAILED', 'Approval ACL restriction failed.');
  fs.renameSync(tempPath, approvalPath);
  if (!auditAcl(approvalPath).ok) fail('KEY_DELIVERY_APPROVAL_ACL_FAILED', 'Final approval ACL audit failed.');
  console.log(JSON.stringify({
    ok: true,
    action: confirmPhrase,
    validatorCount: 4,
    deviceKeyIdsBound: 4,
    expiresInMinutes: 10,
    aclProtected: true,
    controls: approval.controls
  }, null, 2));
}

try {
  main();
} catch (error) {
  console.error(JSON.stringify({ ok: false, error: error.code || 'KEY_DELIVERY_APPROVAL_FAILED', message: error.message }));
  process.exit(1);
}
