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

const root = path.join(__dirname, '..');
const migrationRoot = path.join(root, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const outputRoot = path.join(migrationRoot, 'encrypted-key-envelopes');
const deliveryApprovalPath = path.join(migrationRoot, 'encrypted-key-delivery-approval.json');
const activationApprovalPath = path.join(migrationRoot, 'validator-activation-approval.json');
const confirmPhrase = 'REBIND_ONLY_REINSTALLED_VALIDATOR_ENVELOPE';

function args() {
  return Object.fromEntries(process.argv.slice(2).map(value => {
    const match = String(value).match(/^--([a-z-]+)=(.+)$/i);
    return match ? [match[1], match[2]] : ['', ''];
  }).filter(row => row[0]));
}

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

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

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

function assertInside(parent, candidate) {
  const relative = path.relative(path.resolve(parent), path.resolve(candidate));
  if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
    throw new Error(`Unsafe archive path: ${candidate}`);
  }
}

function validateManifest(manifest, topology, manifestRoot) {
  if (
    manifest.schemaVersion !== 'plais-encrypted-validator-key-delivery/v1'
    || manifest.plaintextKeyFilesWritten !== false
    || manifest.aclProtected !== true
    || Number(manifest.chainId) !== Number(topology.chainId)
    || manifest.genesisSha256 !== topology.genesisSha256
    || !Array.isArray(manifest.nodes)
    || manifest.nodes.length !== 4
  ) throw new Error('Encrypted validator manifest is invalid.');
  for (const row of manifest.nodes) {
    const filePath = path.join(manifestRoot, row.fileName);
    if (!fs.existsSync(filePath) || sha256File(filePath) !== row.sha256) {
      throw new Error(`Encrypted envelope hash mismatch: ${row.nodeName}`);
    }
  }
}

function main() {
  const input = args();
  if (input.confirm !== confirmPhrase) throw new Error('Exact rebind confirmation is required.');
  if (!/^validator-[1-4]$/.test(String(input.node || ''))) throw new Error('Exactly one reinstalled validator is required.');
  if (!/^\d{8}T\d{4}Z$/.test(String(input['archive-id'] || ''))) throw new Error('A safe archive identifier is required.');
  if (fs.existsSync(activationApprovalPath)) throw new Error('Validator activation approval must not exist during envelope rebind.');

  const archiveRoot = path.join(migrationRoot, 'security-artifact-archive', input['archive-id']);
  const oldRoot = path.join(archiveRoot, 'encrypted-key-envelopes');
  const supersededRoot = path.join(archiveRoot, 'superseded-all-random-envelopes');
  const archivedDeliveryApproval = path.join(archiveRoot, 'superseded-delivery-approval.json');
  const tempRoot = path.join(migrationRoot, `encrypted-key-envelopes.rebind-${process.pid}-${Date.now()}`);
  for (const candidate of [archiveRoot, oldRoot, supersededRoot, archivedDeliveryApproval, tempRoot]) {
    assertInside(migrationRoot, candidate);
  }
  if (!fs.existsSync(oldRoot) || !fs.existsSync(outputRoot)) throw new Error('Old or current encrypted envelope set is missing.');
  if (fs.existsSync(supersededRoot) || fs.existsSync(tempRoot)) throw new Error('Rebind destination already exists.');

  const topology = readJson(topologyPath);
  const oldManifest = readJson(path.join(oldRoot, 'manifest.json'));
  const currentManifest = readJson(path.join(outputRoot, 'manifest.json'));
  validateManifest(oldManifest, topology, oldRoot);
  validateManifest(currentManifest, topology, outputRoot);
  const target = topology.validators.find(row => row.nodeName === input.node);
  if (!target) throw new Error('Reinstalled validator target is missing from topology.');

  const stateNodes = persistentStore.readState().nodes || [];
  const selected = topology.validators.map(row => {
    const node = latestNode(stateNodes, row);
    const currentDeviceKeyId = String(node?.metadata?.provisioningKeyId || '');
    if (!/^[a-f0-9]{64}$/.test(currentDeviceKeyId)) throw new Error(`Current device identity is missing: ${row.nodeName}`);
    const sourceManifest = row.nodeName === target.nodeName ? currentManifest : oldManifest;
    const sourceRoot = row.nodeName === target.nodeName ? outputRoot : oldRoot;
    const manifestRow = sourceManifest.nodes.find(entry => entry.nodeName === row.nodeName && entry.pcName === row.pcName);
    if (!manifestRow || manifestRow.deviceKeyId !== currentDeviceKeyId) {
      throw new Error(`Selected envelope is not bound to the current device: ${row.nodeName}`);
    }
    return { row: manifestRow, sourceRoot };
  });

  if (fs.existsSync(deliveryApprovalPath)) {
    const approval = readJson(deliveryApprovalPath);
    if (approval.action !== 'DELIVER_ENCRYPTED_VALIDATOR_KEY_ENVELOPES') throw new Error('Unexpected delivery approval is present.');
    if (fs.existsSync(archivedDeliveryApproval)) throw new Error('Archived delivery approval already exists.');
    fs.renameSync(deliveryApprovalPath, archivedDeliveryApproval);
    if (!restrictAcl(archivedDeliveryApproval, false).ok) throw new Error('Archived delivery approval ACL failed.');
  }

  fs.renameSync(outputRoot, supersededRoot);
  fs.mkdirSync(tempRoot, { recursive: false });
  if (!restrictAcl(tempRoot, true).ok) throw new Error('Composite envelope directory ACL failed.');
  for (const selection of selected) {
    const effectiveSourceRoot = selection.sourceRoot === outputRoot ? supersededRoot : selection.sourceRoot;
    const sourcePath = path.join(effectiveSourceRoot, selection.row.fileName);
    const destinationPath = path.join(tempRoot, selection.row.fileName);
    fs.copyFileSync(sourcePath, destinationPath, fs.constants.COPYFILE_EXCL);
    if (sha256File(destinationPath) !== selection.row.sha256 || !restrictAcl(destinationPath, false).ok) {
      throw new Error(`Composite envelope copy failed: ${selection.row.nodeName}`);
    }
  }
  const compositeManifest = {
    ...currentManifest,
    nodes: selected.map(selection => selection.row),
    createdAt: new Date().toISOString()
  };
  const manifestPath = path.join(tempRoot, 'manifest.json');
  fs.writeFileSync(manifestPath, `${JSON.stringify(compositeManifest, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
  if (!restrictAcl(manifestPath, false).ok) throw new Error('Composite manifest ACL failed.');
  fs.renameSync(tempRoot, outputRoot);
  if (!restrictAcl(outputRoot, true).ok) throw new Error('Final composite directory ACL failed.');

  console.log(JSON.stringify({
    ok: true,
    state: 'ENCRYPTED_ENVELOPES_REBOUND_AFTER_SINGLE_DEVICE_REINSTALL',
    reusedEnvelopeCount: selected.filter(selection => selection.row.nodeName !== target.nodeName).length,
    replacedEnvelopeCount: 1,
    replacedNodeName: target.nodeName,
    plaintextKeyFilesWritten: false,
    oldArtifactsPreserved: true,
    standingDeliveryApproval: false,
    aclProtected: true
  }, null, 2));
}

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