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

const root = path.join(__dirname, '..');
const migrationRoot = path.join(root, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const envelopeManifestPath = path.join(migrationRoot, 'encrypted-key-envelopes', 'manifest.json');
const runtimeManifestPath = path.join(root, 'runtime', 'plais-validator-runtime', 'manifest.json');
const approvalPath = path.join(migrationRoot, 'validator-activation-approval.json');

const readJson = filePath => JSON.parse(fs.readFileSync(filePath, 'utf8'));
const sha256File = filePath => crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');

function bindingFor(node) {
  if (![topologyPath, envelopeManifestPath, runtimeManifestPath].every(fs.existsSync)) return null;
  const topology = readJson(topologyPath);
  const envelopes = readJson(envelopeManifestPath);
  const runtime = readJson(runtimeManifestPath);
  const pcName = String(node?.pcName || node?.metadata?.hostname || '').trim();
  const target = findValidatorTarget(topology, node);
  const envelope = (envelopes.nodes || []).find(row => row.nodeName === target?.nodeName && row.pcName === target?.pcName);
  const deviceKeyId = String(node?.metadata?.provisioningKeyId || '').toLowerCase();
  if (
    !target || !envelope
    || topology.mode !== 'preserve-canonical-genesis'
    || Number(topology.chainId) !== 20260718
    || envelopes.genesisSha256 !== topology.genesisSha256
    || envelope.deviceKeyId !== deviceKeyId
    || !/^[a-f0-9]{64}$/.test(deviceKeyId)
    || runtime.schemaVersion !== 'plais-validator-runtime-artifact/v1'
    || runtime.contents?.privateKeysIncluded !== false
  ) return null;
  return { node, topology, envelopes, runtime, target, envelope, pcName, deviceKeyId };
}

function validateApproval(binding) {
  if (!fs.existsSync(approvalPath)) return null;
  try {
    const approval = readJson(approvalPath);
    const approvedAt = new Date(approval.approvedAt || 0).getTime();
    const expiresAt = new Date(approval.expiresAt || 0).getTime();
    const now = Date.now();
    const controls = approval.controls || {};
    const availableOtherValidators = Array.isArray(approval.availableOtherValidators)
      ? approval.availableOtherValidators
      : [];
    const availabilityValid = availableOtherValidators.length === 3
      && new Set(availableOtherValidators.map(row => String(row?.nodeName || ''))).size === 3
      && availableOtherValidators.every(row => (
        /^validator-[1-4]$/.test(String(row?.nodeName || ''))
        && row.nodeName !== binding.target.nodeName
        && row.available === true
        && (row.centralOnline === true || row.remoteActive === true)
      ));
    const valid = approval.schemaVersion === 'plais-validator-activation-approval/v1'
      && approval.action === 'ACTIVATE_REMOTE_VALIDATOR_AFTER_CANONICAL_STOP'
      && approval.nodeName === binding.target.nodeName
      && String(approval.pcName || '').toLowerCase() === binding.pcName.toLowerCase()
      && approval.deviceKeyId === binding.deviceKeyId
      && approval.validatorAddress === binding.target.validatorAddress
      && approval.topologySha256 === sha256File(topologyPath)
      && approval.envelopeManifestSha256 === sha256File(envelopeManifestPath)
      && approval.runtimeManifestSha256 === sha256File(runtimeManifestPath)
      && approval.envelopeSha256 === binding.envelope.sha256
      && approval.runtimeArtifactSha256 === binding.runtime.sha256
      && approval.centralValidatorStopped === true
      && Number.isInteger(Number(approval.remainingCentralValidatorCount))
      && Number(approval.remainingCentralValidatorCount) >= 0
      && Number(approval.remainingCentralValidatorCount) <= 3
      && availabilityValid
      && controls.validatorKeyDecryptAllowed === true
      && controls.validatorKeyImportAllowed === true
      && controls.validatorStartAllowed === true
      && controls.publicRpcAllowed === false
      && controls.externalTransactionAllowed === false
      && controls.operatorSigningAllowed === false
      && controls.arbitraryCommandAllowed === 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 + 20 * 60_000;
    return valid ? approval : null;
  } catch (_) { return null; }
}

function buildOffer(node) {
  const binding = bindingFor(node);
  if (!binding) return { ok: false, status: 404, reason: 'VALIDATOR_ACTIVATION_TARGET_NOT_FOUND' };
  const approval = validateApproval(binding);
  const issuedAt = new Date();
  return {
    ok: true,
    offer: {
      schemaVersion: 'plais-validator-activation-offer/v1',
      nodeId: String(node.nodeId || node.id || ''),
      pcName: binding.pcName,
      nodeName: binding.target.nodeName,
      chainId: Number(binding.topology.chainId),
      genesisSha256: binding.topology.genesisSha256,
      deviceKeyId: binding.deviceKeyId,
      validatorAddress: binding.target.validatorAddress,
      validatorPublicKey: binding.target.publicKey,
      envelopeSha256: binding.envelope.sha256,
      runtimeArtifactSha256: binding.runtime.sha256,
      localRpcHost: '127.0.0.1',
      localRpcPort: 18545,
      state: approval ? 'approved-awaiting-validator-activation' : 'locked-awaiting-validator-activation-approval',
      controls: {
        validatorKeyDecryptAllowed: Boolean(approval),
        validatorKeyImportAllowed: Boolean(approval),
        validatorStartAllowed: Boolean(approval),
        publicRpcAllowed: false,
        externalTransactionAllowed: false,
        operatorSigningAllowed: false,
        arbitraryCommandAllowed: false
      },
      approvalId: approval ? approval.approvalId : '',
      issuedAt: issuedAt.toISOString(),
      expiresAt: new Date(issuedAt.getTime() + 5 * 60_000).toISOString()
    }
  };
}

module.exports = { buildOffer, paths: { approvalPath } };
