const crypto = require('crypto');
const fs = require('fs');
const net = require('net');
const path = require('path');
const { findValidatorTarget } = require('./validatorTargetBindingService');

const topologyPath = path.join(__dirname, '..', 'runtime', 'plais-qbft-canonical-migration', 'topology.json');
const approvalPath = path.join(__dirname, '..', 'runtime', 'plais-qbft-canonical-migration', 'validator-tunnel-migration-approval.json');
const PROTOCOL = 'plais-validator-p2p-tunnel/v1';
const DIRECT_MESH_SCHEMA = 'plais-validator-direct-quorum/v1';

function isPrivateOverlayIpv4(value) {
  const address = String(value || '').trim();
  if (net.isIP(address) !== 4) return false;
  const parts = address.split('.').map(Number);
  return parts[0] === 10
    || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
    || (parts[0] === 192 && parts[1] === 168)
    || (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127);
}

function getDirectQuorum(topology) {
  const direct = topology?.directQuorum;
  const targetNodeNames = Array.isArray(direct?.targetNodeNames)
    ? direct.targetNodeNames.map(value => String(value || ''))
    : [];
  const targets = targetNodeNames.map(nodeName => (topology.validators || []).find(row => row.nodeName === nodeName));
  if (
    direct?.schemaVersion !== DIRECT_MESH_SCHEMA
    || !['STAGED', 'ACTIVE'].includes(String(direct?.state || ''))
    || JSON.stringify(targetNodeNames) !== JSON.stringify(['validator-1', 'validator-2', 'validator-3'])
    || targets.some(target => !target || !isPrivateOverlayIpv4(target.targetHost) || Number(target.p2pPort) !== 30303)
    || new Set(targets.map(target => target.targetHost)).size !== 3
  ) return null;
  return { ...direct, targetNodeNames, targets };
}

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

function restartApprovalValidForNode(approval, nodeName, topologySha256, now = Date.now()) {
    const single = approval.action === 'RESTART_SINGLE_VALIDATOR_WITH_DIRECT_TUNNEL_PEERS'
      && approval.nodeName === nodeName
      && !approval.targetNodeNames
      && approval.controls?.coordinatedConsensusRecovery !== true;
    const coordinatedTargets = Array.isArray(approval.targetNodeNames)
      ? approval.targetNodeNames.map(value => String(value || ''))
      : [];
    const coordinated = approval.action === 'RESTART_VALIDATOR_SET_FOR_QBFT_CONSENSUS_RECOVERY'
      && JSON.stringify(coordinatedTargets) === JSON.stringify(['validator-1', 'validator-2', 'validator-3', 'validator-4'])
      && coordinatedTargets.includes(nodeName)
      && approval.controls?.coordinatedConsensusRecovery === true
      && Number(approval.controls?.coordinatedRestartMaxSkewSeconds) === 180;
    const directTargets = Array.isArray(approval.targetNodeNames)
      ? approval.targetNodeNames.map(value => String(value || ''))
      : [];
    const directQuorum = approval.action === 'ACTIVATE_VALIDATOR_DIRECT_LAN_QUORUM'
      && JSON.stringify(directTargets) === JSON.stringify(['validator-1', 'validator-2', 'validator-3'])
      && directTargets.includes(nodeName)
      && approval.controls?.directLanQuorumMigration === true
      && Number(approval.controls?.coordinatedRestartMaxSkewSeconds) === 180;
    const approvedAt = new Date(approval.approvedAt || 0).getTime();
    const expiresAt = new Date(approval.expiresAt || 0).getTime();
    return approval.schemaVersion === 'plais-validator-tunnel-migration-approval/v1'
      && (single || coordinated || directQuorum)
      && approval.topologySha256 === topologySha256
      && approval.controls?.validatorProcessRestartAllowed === true
      && approval.controls?.validatorKeyReadForIdentityCheckOnly === true
      && approval.controls?.validatorKeyExportAllowed === false
      && approval.controls?.publicRpcAllowed === false
      && approval.controls?.externalTransactionAllowed === false
      && approval.controls?.operatorSigningAllowed === false
      && /^[A-Za-z0-9._:-]{8,128}$/.test(String(approval.approvalId || ''))
      && Number.isFinite(approvedAt) && approvedAt <= now && approvedAt >= now - 30 * 60_000
      && Number.isFinite(expiresAt) && expiresAt > now && expiresAt <= approvedAt + 15 * 60_000;
}

function validateRestartApproval(nodeName) {
  try {
    const approval = JSON.parse(fs.readFileSync(approvalPath, 'utf8'));
    return restartApprovalValidForNode(approval, nodeName, sha256File(topologyPath)) ? approval : null;
  } catch (_) { return null; }
}

function directAttestationApprovalValidForNode(approval, nodeName, topologySha256, now = Date.now()) {
  const targets = Array.isArray(approval?.targetNodeNames)
    ? approval.targetNodeNames.map(value => String(value || '')) : [];
  const allTargets = approval?.action === 'ATTEST_EXISTING_VALIDATOR_DIRECT_LAN_QUORUM'
    && JSON.stringify(targets) === JSON.stringify(['validator-1', 'validator-2', 'validator-3'])
    && targets.includes(nodeName);
  const singleTarget = approval?.action === 'ATTEST_EXISTING_SINGLE_VALIDATOR_DIRECT_LAN'
    && approval.nodeName === nodeName && !approval.targetNodeNames;
  const approvedAt = new Date(approval?.approvedAt || 0).getTime();
  const expiresAt = new Date(approval?.expiresAt || 0).getTime();
  return approval?.schemaVersion === 'plais-validator-tunnel-migration-approval/v1'
    && (allTargets || singleTarget)
    && approval.topologySha256 === topologySha256
    && approval.controls?.directLanQuorumAttestation === true
    && approval.controls?.validatorProcessRestartAllowed === false
    && approval.controls?.validatorKeyReadForIdentityCheckOnly === true
    && approval.controls?.validatorKeyExportAllowed === false
    && approval.controls?.publicRpcAllowed === false
    && approval.controls?.externalTransactionAllowed === false
    && approval.controls?.operatorSigningAllowed === false
    && /^[A-Za-z0-9._:-]{8,128}$/.test(String(approval.approvalId || ''))
    && Number.isFinite(approvedAt) && approvedAt <= now && approvedAt >= now - 30 * 60_000
    && Number.isFinite(expiresAt) && expiresAt > now && expiresAt <= approvedAt + 15 * 60_000;
}

function validateDirectAttestationApproval(nodeName) {
  try {
    const approval = JSON.parse(fs.readFileSync(approvalPath, 'utf8'));
    return directAttestationApprovalValidForNode(approval, nodeName, sha256File(topologyPath)) ? approval : null;
  } catch (_) { return null; }
}

function buildOffer(node) {
  if (!fs.existsSync(topologyPath)) return { ok: false, status: 503, reason: 'VALIDATOR_TUNNEL_TOPOLOGY_MISSING' };
  const topology = JSON.parse(fs.readFileSync(topologyPath, 'utf8'));
  const pcName = String(node?.pcName || node?.metadata?.hostname || '').trim();
  const target = findValidatorTarget(topology, node);
  const deviceKeyId = String(node?.metadata?.provisioningKeyId || '').trim().toLowerCase();
  if (
    topology.mode !== 'preserve-canonical-genesis'
    || Number(topology.chainId) !== 20260718
    || !target
    || node?.metadata?.chainProvisioningRole !== 'validator-candidate'
    || !/^[a-f0-9]{64}$/.test(deviceKeyId)
  ) return { ok: false, status: 404, reason: 'VALIDATOR_TUNNEL_TARGET_NOT_FOUND' };
  const peers = (topology.validators || []).filter(row => row.nodeName !== target.nodeName).map(row => ({
    nodeName: row.nodeName,
    validatorPublicKey: String(row.publicKey || '').replace(/^0x/, ''),
    localProxyPort: 31300 + Number(String(row.nodeName).split('-')[1])
  }));
  if (peers.length !== 3 || peers.some(row => !/^[a-f0-9]{128}$/i.test(row.validatorPublicKey))) {
    return { ok: false, status: 503, reason: 'VALIDATOR_TUNNEL_PEERS_INVALID' };
  }
  const issuedAt = new Date();
  const restartApproval = validateRestartApproval(target.nodeName);
  const directAttestationApproval = validateDirectAttestationApproval(target.nodeName);
  const directAuthorization = restartApproval || directAttestationApproval;
  const directQuorum = getDirectQuorum(topology);
  const directTarget = directQuorum?.targets.find(row => row.nodeName === target.nodeName) || null;
  const directAlreadyApplied = node?.metadata?.validatorTunnelState === 'direct-lan-quorum-with-tunnel-fallback-applied';
  const directEnabled = Boolean(directTarget
    && (directQuorum.state === 'ACTIVE'
      || restartApproval?.action === 'ACTIVATE_VALIDATOR_DIRECT_LAN_QUORUM'
      || (restartApproval?.action === 'RESTART_SINGLE_VALIDATOR_WITH_DIRECT_TUNNEL_PEERS'
        && restartApproval.nodeName === target.nodeName)
      || directAttestationApproval?.action === 'ATTEST_EXISTING_VALIDATOR_DIRECT_LAN_QUORUM'
      || directAttestationApproval?.action === 'ATTEST_EXISTING_SINGLE_VALIDATOR_DIRECT_LAN'
      || directAlreadyApplied));
  const directPeers = directEnabled ? directQuorum.targets
    .filter(row => row.nodeName !== target.nodeName)
    .map(row => ({
      nodeName: row.nodeName,
      validatorPublicKey: String(row.publicKey || '').replace(/^0x/, ''),
      host: row.targetHost,
      p2pPort: Number(row.p2pPort)
    })) : [];
  return { ok: true, offer: {
    schemaVersion: 'plais-validator-p2p-tunnel-offer/v1',
    protocol: PROTOCOL,
    nodeId: String(node.nodeId || node.id || ''),
    pcName,
    nodeName: target.nodeName,
    validatorPublicKey: String(target.publicKey || '').replace(/^0x/, ''),
    deviceKeyId,
    chainId: Number(topology.chainId),
    genesisSha256: String(topology.genesisSha256 || ''),
    websocketPath: '/sync/validator-tunnel',
    listenHost: '127.0.0.1',
    besuTargetHost: '127.0.0.1',
    besuTargetPort: 30303,
    peers,
    directMesh: {
      schemaVersion: DIRECT_MESH_SCHEMA,
      enabled: directEnabled,
      localHost: directEnabled ? directTarget.targetHost : '',
      p2pPort: directEnabled ? Number(directTarget.p2pPort) : 0,
      quorumNodeNames: directEnabled ? directQuorum.targetNodeNames : [],
      peers: directPeers,
      tunnelFallbackAllowed: true
    },
    controls: {
      p2pTunnelAllowed: true,
      directMeshActivationAllowed: directEnabled,
      directRuntimeAttestationAllowed: Boolean(directAttestationApproval),
      presyncProcessRestartAllowed: true,
      validatorProcessRestartAllowed: Boolean(restartApproval),
      validatorRestartApprovalId: directAuthorization ? directAuthorization.approvalId : '',
      validatorKeyReadForIdentityCheckOnly: Boolean(directAuthorization),
      validatorKeyExportAllowed: false,
      publicRpcAllowed: false,
      externalTransactionAllowed: false,
      operatorSigningAllowed: false,
      validatorKeyAccessAllowed: false,
      arbitraryTargetAllowed: false
    },
    issuedAt: issuedAt.toISOString(),
    expiresAt: new Date(issuedAt.getTime() + 5 * 60_000).toISOString()
  } };
}

module.exports = { buildOffer, getDirectQuorum, restartApprovalValidForNode, directAttestationApprovalValidForNode,
  paths: { approvalPath, topologyPath } };
