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

const CONFIRMATION = 'STAGE validator-1,validator-2,validator-3 DIRECT LAN QUORUM';
const confirm = String(process.argv.find(value => value.startsWith('--confirm=')) || '').slice(10);
if (confirm !== CONFIRMATION) throw new Error('Exact direct LAN quorum staging confirmation is required.');
if (fs.existsSync(service.paths.approvalPath)) throw new Error('A validator migration approval is already present.');

function privateLanIpv4(value) {
  const address = String(value || '').trim();
  if (net.isIP(address) !== 4) return false;
  const [a, b] = address.split('.').map(Number);
  return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
}

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

const topology = JSON.parse(fs.readFileSync(service.paths.topologyPath, 'utf8'));
const targetNodeNames = ['validator-1', 'validator-2', 'validator-3'];
const targets = targetNodeNames.map(nodeName => (topology.validators || []).find(row => row.nodeName === nodeName));
if (targets.some(target => !target)) throw new Error('The direct LAN quorum targets are incomplete.');
const nodes = persistentStore.readState().nodes || [];
const evidence = targets.map(target => {
  const node = latest(nodes, target);
  const addresses = (Array.isArray(node?.metadata?.privateNetworkAddresses) ? node.metadata.privateNetworkAddresses : [])
    .map(String).filter(privateLanIpv4);
  const heartbeatAgeMs = Date.now() - new Date(node?.lastHeartbeatAt || 0).getTime();
  const publicEgressAddress = String(node?.metadata?.publicEgressAddress || '');
  if (node?.status !== 'active' || heartbeatAgeMs < 0 || heartbeatAgeMs > 3 * 60_000
    || addresses.length !== 1 || !publicEgressAddress) {
    throw new Error(`Fresh single-address LAN evidence is incomplete: ${target.nodeName}.`);
  }
  return { target, node, host: addresses[0], publicEgressAddress };
});
if (new Set(evidence.map(row => row.host)).size !== 3) throw new Error('Direct LAN target addresses must be unique.');
if (new Set(evidence.map(row => row.publicEgressAddress)).size !== 1) {
  throw new Error('Direct LAN quorum targets do not share the same observed WAN boundary.');
}
const subnetPrefixes = new Set(evidence.map(row => row.host.split('.').slice(0, 3).join('.')));
if (subnetPrefixes.size !== 1) throw new Error('Direct LAN quorum targets are not on the same IPv4 /24 subnet.');

for (const row of evidence) {
  row.target.targetHost = row.host;
  row.target.p2pPort = 30303;
}
const stagedAt = new Date().toISOString();
const next = {
  ...topology,
  directQuorum: {
    schemaVersion: 'plais-validator-direct-quorum/v1',
    state: 'STAGED',
    targetNodeNames,
    requiredConsensusNodes: 3,
    networkBoundaryEvidence: 'same-server-observed-public-egress-and-private-/24',
    stagedAt
  },
  updatedAt: stagedAt
};
if (!service.getDirectQuorum(next)) throw new Error('Generated direct LAN quorum failed validation.');
const historyRoot = path.join(path.dirname(service.paths.topologyPath), 'topology-history');
fs.mkdirSync(historyRoot, { recursive: true });
const backupPath = path.join(historyRoot, `topology-before-direct-lan-${Date.now()}.json`);
fs.copyFileSync(service.paths.topologyPath, backupPath, fs.constants.COPYFILE_EXCL);
const temporaryPath = `${service.paths.topologyPath}.${process.pid}.tmp`;
fs.writeFileSync(temporaryPath, `${JSON.stringify(next, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
fs.renameSync(temporaryPath, service.paths.topologyPath);
console.log(JSON.stringify({
  ok: true,
  status: 'DIRECT_LAN_QUORUM_STAGED_NOT_ACTIVATED',
  targetNodeNames,
  targetHosts: evidence.map(row => ({ nodeName: row.target.nodeName, host: row.host })),
  topologySha256: crypto.createHash('sha256').update(fs.readFileSync(service.paths.topologyPath)).digest('hex'),
  backupPath,
  validatorChanged: false
}, null, 2));
