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

const projectRoot = path.join(__dirname, '..');
const migrationRoot = path.join(projectRoot, 'runtime', 'plais-qbft-canonical-migration');
const topologyPath = path.join(migrationRoot, 'topology.json');
const envelopeRoot = path.join(migrationRoot, 'encrypted-key-envelopes');
const manifestPath = path.join(envelopeRoot, 'manifest.json');
const deliveryApprovalPath = path.join(migrationRoot, 'encrypted-key-delivery-approval.json');

function stableValue(value) {
  if (Array.isArray(value)) return value.map(stableValue);
  if (value && typeof value === 'object') {
    return Object.keys(value).sort().reduce((out, key) => {
      out[key] = stableValue(value[key]);
      return out;
    }, {});
  }
  return value;
}

function sha256(value) {
  return crypto.createHash('sha256').update(value).digest('hex');
}

function sha256File(filePath) {
  return sha256(fs.readFileSync(filePath));
}

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

function getTarget(node) {
  if (!node || !fs.existsSync(topologyPath) || !fs.existsSync(manifestPath)) return null;
  const topology = readJson(topologyPath);
  const manifest = readJson(manifestPath);
  const pcName = String(node.pcName || node.metadata?.hostname || '').trim();
  const target = findValidatorTarget(topology, node);
  if (!target) return null;
  const manifestRow = (manifest.nodes || []).find((row) => row.nodeName === target.nodeName && row.pcName === target.pcName);
  if (!manifestRow) return null;
  const deviceKeyId = String(node.metadata?.provisioningKeyId || '').trim().toLowerCase();
  if (!/^[a-f0-9]{64}$/.test(deviceKeyId) || manifestRow.deviceKeyId !== deviceKeyId) return null;
  const envelopePath = path.join(envelopeRoot, manifestRow.fileName);
  if (!fs.existsSync(envelopePath) || sha256File(envelopePath) !== manifestRow.sha256) return null;
  return { topology, manifest, target, manifestRow, envelopePath, pcName, deviceKeyId };
}

function validateDeliveryApproval(binding) {
  if (!fs.existsSync(deliveryApprovalPath)) return { ok: false, reason: 'DELIVERY_APPROVAL_MISSING' };
  try {
    const approval = readJson(deliveryApprovalPath);
    const now = Date.now();
    const approvedAt = new Date(approval.approvedAt || 0).getTime();
    const expiresAt = new Date(approval.expiresAt || 0).getTime();
    const expectedDeviceKeyIds = Object.fromEntries((binding.manifest.nodes || []).map((row) => [row.nodeName, row.deviceKeyId]));
    const valid = approval.action === 'DELIVER_ENCRYPTED_VALIDATOR_KEY_ENVELOPES'
      && approval.topologySha256 === sha256File(topologyPath)
      && approval.manifestSha256 === sha256File(manifestPath)
      && approval.genesisSha256 === binding.topology.genesisSha256
      && JSON.stringify(stableValue(approval.deviceKeyIds || {})) === JSON.stringify(stableValue(expectedDeviceKeyIds))
      && /^[A-Za-z0-9._:-]{8,128}$/.test(String(approval.approvalId || ''))
      && Boolean(String(approval.approvedBy || '').trim())
      && Number.isFinite(approvedAt)
      && approvedAt <= now
      && approvedAt >= now - 24 * 60 * 60 * 1000
      && Number.isFinite(expiresAt)
      && expiresAt > now
      && expiresAt <= now + 24 * 60 * 60 * 1000;
    return valid ? { ok: true, approval } : { ok: false, reason: 'DELIVERY_APPROVAL_INVALID' };
  } catch (error) {
    return { ok: false, reason: 'DELIVERY_APPROVAL_INVALID' };
  }
}

function buildOffer(node) {
  const binding = getTarget(node);
  if (!binding) return { ok: false, status: 404, reason: 'VALIDATOR_ENVELOPE_TARGET_NOT_FOUND' };
  const approval = validateDeliveryApproval(binding);
  const issuedAt = new Date();
  const envelopeBytes = fs.statSync(binding.envelopePath).size;
  const envelopeId = sha256(Buffer.from(JSON.stringify(stableValue({
    topologySha256: sha256File(topologyPath),
    manifestSha256: sha256File(manifestPath),
    nodeName: binding.target.nodeName,
    pcName: binding.pcName,
    deviceKeyId: binding.deviceKeyId,
    envelopeSha256: binding.manifestRow.sha256
  }))));
  return {
    ok: true,
    offer: {
      schemaVersion: 'plais-validator-key-delivery-offer/v1',
      nodeId: String(node.nodeId || node.id || ''),
      pcName: binding.pcName,
      nodeName: binding.target.nodeName,
      chainId: Number(binding.topology.chainId),
      genesisSha256: binding.topology.genesisSha256,
      deviceKeyAlgorithm: 'rsa-oaep-sha256-dpapi-current-user-v1',
      deviceKeyId: binding.deviceKeyId,
      envelopeId,
      envelopeSha256: binding.manifestRow.sha256,
      envelopeBytes,
      state: approval.ok ? 'approved-awaiting-encrypted-download' : 'locked-awaiting-delivery-approval',
      controls: {
        encryptedEnvelopeDownloadAllowed: approval.ok,
        validatorKeyDecryptAllowed: false,
        validatorKeyImportAllowed: false,
        validatorStartAllowed: false,
        arbitraryCommandAllowed: false
      },
      approvalId: approval.ok ? String(approval.approval.approvalId || '') : '',
      issuedAt: issuedAt.toISOString(),
      expiresAt: new Date(issuedAt.getTime() + 10 * 60 * 1000).toISOString()
    }
  };
}

function getEncryptedEnvelope(node) {
  const offerResult = buildOffer(node);
  if (!offerResult.ok) return offerResult;
  if (offerResult.offer.controls.encryptedEnvelopeDownloadAllowed !== true) {
    return { ok: false, status: 423, reason: 'VALIDATOR_KEY_DELIVERY_LOCKED', offer: offerResult.offer };
  }
  const binding = getTarget(node);
  const envelopeBytes = fs.readFileSync(binding.envelopePath);
  if (sha256(envelopeBytes) !== offerResult.offer.envelopeSha256) {
    return { ok: false, status: 409, reason: 'VALIDATOR_ENVELOPE_HASH_CHANGED' };
  }
  return {
    ok: true,
    payload: {
      schemaVersion: 'plais-validator-key-encrypted-delivery/v1',
      offer: offerResult.offer,
      envelopeFileBase64: envelopeBytes.toString('base64'),
      controls: {
        validatorKeyDecryptAllowed: false,
        validatorKeyImportAllowed: false,
        validatorStartAllowed: false,
        arbitraryCommandAllowed: false
      }
    }
  };
}

module.exports = { buildOffer, getEncryptedEnvelope };
