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

const projectRoot = path.join(__dirname, '..');
const activeRoot = path.join(projectRoot, 'runtime', 'plais-qbft');
const migrationRoot = path.join(projectRoot, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const approvalPath = path.join(migrationRoot, 'encrypted-key-export-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'
]);
const confirmPhrase = 'ENCRYPT_CANONICAL_VALIDATOR_KEYS';

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 getDeviceKeyIds(topology) {
  const nodes = persistentStore.readState().nodes || [];
  const now = Date.now();
  const entries = (topology.validators || []).map((target) => {
    const node = latestNodeForTarget(nodes, target);
    const metadata = node?.metadata || {};
    const heartbeatAgeMs = node?.lastHeartbeatAt ? now - new Date(node.lastHeartbeatAt).getTime() : Infinity;
    const ready = Boolean(node)
      && node.status === 'active'
      && Number.isFinite(heartbeatAgeMs)
      && heartbeatAgeMs <= 5 * 60 * 1000
      && supportedClientVersions.has(String(node.clientVersion || ''))
      && metadata.chainProvisioningSchema === 'plais-node-provisioning/v1'
      && /^[a-f0-9]{64}$/.test(String(metadata.chainProvisioningHash || ''))
      && metadata.chainProvisioningRole === 'validator-candidate'
      && metadata.chainProvisioningState === 'awaiting-operator-approval'
      && metadata.provisioningKeyAlgorithm === 'rsa-oaep-sha256-dpapi-current-user-v1'
      && /^[a-f0-9]{64}$/.test(String(metadata.provisioningKeyId || ''))
      && Boolean(metadata.provisioningPublicKey);
    if (!ready) fail('VALIDATOR_CLIENT_NOT_READY', `Validator candidate is not ready: ${target.pcName}`);
    parseDevicePublicKey(metadata.provisioningPublicKey, metadata.provisioningKeyId);
    return [target.nodeName, metadata.provisioningKeyId];
  });
  return Object.fromEntries(entries);
}

function main() {
  const args = parseArguments();
  if (args.confirm !== confirmPhrase) fail('KEY_ENCRYPTION_CONFIRMATION_REQUIRED', 'Exact key encryption confirmation is required.');
  if (!/^[A-Za-z0-9._:-]{8,128}$/.test(String(args['approved-by'] || ''))) {
    fail('KEY_ENCRYPTION_APPROVER_INVALID', 'A valid approved-by ceremony identifier is required.');
  }
  if (fs.existsSync(approvalPath)) fail('KEY_ENCRYPTION_APPROVAL_EXISTS', 'A key encryption approval already exists.');
  const topology = JSON.parse(fs.readFileSync(topologyPath, 'utf8'));
  if (topology.mode !== 'preserve-canonical-genesis' || !Array.isArray(topology.validators) || topology.validators.length !== 4) {
    fail('KEY_ENCRYPTION_TOPOLOGY_INVALID', 'Canonical four-validator topology is invalid.');
  }
  const genesisPath = path.join(activeRoot, 'generated', 'genesis.json');
  const genesisSha256 = sha256File(genesisPath);
  if (genesisSha256 !== topology.genesisSha256) fail('KEY_ENCRYPTION_GENESIS_CHANGED', 'Canonical genesis hash changed.');
  const approvedAt = new Date();
  const approval = {
    action: confirmPhrase,
    approvalId: `validator-key-encryption-${approvedAt.getTime()}-${crypto.randomBytes(6).toString('hex')}`,
    approvedBy: args['approved-by'],
    approvedAt: approvedAt.toISOString(),
    expiresAt: new Date(approvedAt.getTime() + 30 * 60 * 1000).toISOString(),
    topologySha256: sha256File(topologyPath),
    genesisSha256,
    deviceKeyIds: getDeviceKeyIds(topology)
  };
  const tempPath = `${approvalPath}.tmp-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
  fs.writeFileSync(tempPath, `${JSON.stringify(approval, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  const tempAcl = restrictAcl(tempPath, false);
  if (!tempAcl.ok) fail('KEY_ENCRYPTION_APPROVAL_ACL_FAILED', 'Approval ACL restriction failed.');
  fs.renameSync(tempPath, approvalPath);
  const acl = auditAcl(approvalPath);
  if (!acl.ok) fail('KEY_ENCRYPTION_APPROVAL_ACL_FAILED', 'Final approval ACL audit failed.');
  console.log(JSON.stringify({
    ok: true,
    action: confirmPhrase,
    validatorCount: topology.validators.length,
    deviceKeyIdsBound: Object.keys(approval.deviceKeyIds).length,
    topologyBound: true,
    genesisBound: true,
    expiresInMinutes: 30,
    aclProtected: true,
    liveValidatorKeysRead: false
  }));
}

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