const NodeParticipant = require('../models/NodeParticipant');
const crypto = require('crypto');
const fs = require('fs');
const net = require('net');
const path = require('path');
const NodeEconomyLedger = require('../models/NodeEconomyLedger');
const chainTransactionService = require('./chainTransactionService');
const persistentStore = require('./persistentStore');
const pointLedgerService = require('./pointLedgerService');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');
const { findValidatorTarget } = require('./validatorTargetBindingService');

const state = persistentStore.readState();
const nodes = state.nodes;
const economyEntries = state.nodeEconomyEntries;
const canonicalMigrationTopologyPath = path.join(
  __dirname,
  '..',
  'runtime',
  'plais-qbft-canonical-migration',
  'topology.json'
);
const publicBridgeConfigPath = path.join(
  __dirname,
  '..',
  'runtime',
  'plais-qbft-public-bridge',
  'config.json'
);
const publicBridgeKeyPath = path.join(
  __dirname,
  '..',
  'runtime',
  'plais-qbft-public-bridge',
  'data',
  'key.pub'
);

const defaultNodes = [
  { serviceName: 'AISFOR', endpoint: 'mock://aisfor', role: 'PROOF_PRODUCER', status: 'active' },
  { serviceName: 'AISTALK', endpoint: 'mock://aistalk', role: 'PROOF_PRODUCER', status: 'active' },
  { serviceName: 'AISGROUP', endpoint: 'mock://aisgroup', role: 'PROOF_PRODUCER', status: 'active' },
  { serviceName: 'AISMALL', endpoint: 'mock://aismall', role: 'PROOF_PRODUCER', status: 'active' }
];

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 isPublicIpv4(value) {
  const address = String(value || '').trim();
  if (net.isIP(address) !== 4) return false;
  const parts = address.split('.').map(Number);
  return parts[0] !== 0
    && parts[0] !== 10
    && parts[0] !== 127
    && !(parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127)
    && !(parts[0] === 169 && parts[1] === 254)
    && !(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
    && !(parts[0] === 192 && parts[1] === 168)
    && parts[0] < 224;
}

function sanitizeNodeRegistration(payload = {}) {
  const metadata = payload.metadata && typeof payload.metadata === 'object' && !Array.isArray(payload.metadata)
    ? { ...payload.metadata }
    : {};
  const privateNetworkAddresses = [...new Set(
    (Array.isArray(metadata.privateNetworkAddresses) ? metadata.privateNetworkAddresses : [])
      .map((value) => String(value || '').trim())
      .filter(isPrivateOverlayIpv4)
  )].slice(0, 8);
  const gatewayPort = Number(metadata.networkGatewayPort || 0);
  metadata.privateNetworkAddresses = privateNetworkAddresses;
  metadata.networkGatewayPort = Number.isInteger(gatewayPort) && gatewayPort >= 1024 && gatewayPort <= 65535
    ? gatewayPort
    : 0;
  metadata.networkEvidenceSource = privateNetworkAddresses.length
    && metadata.networkEvidenceSource === 'signed-aisfor-gateway-registration'
    ? metadata.networkEvidenceSource
    : '';
  metadata.networkEvidenceRecordedAt = privateNetworkAddresses.length ? new Date().toISOString() : null;
  const publicEgressAddress = String(metadata.publicEgressAddress || '').trim();
  metadata.publicEgressAddress = isPublicIpv4(publicEgressAddress) ? publicEgressAddress : '';
  metadata.publicEgressEvidenceSource = metadata.publicEgressAddress
    && metadata.publicEgressEvidenceSource === 'server-observed-sync-request-v1'
    ? metadata.publicEgressEvidenceSource
    : '';
  metadata.publicEgressEvidenceRecordedAt = metadata.publicEgressAddress ? new Date().toISOString() : null;
  const transportPort = Number(metadata.chainTransportBridgePort || 0);
  const transportHost = String(metadata.chainTransportBridgeHost || '').trim();
  const transportEnodeHash = String(metadata.chainTransportBridgeEnodeHash || '').trim().toLowerCase();
  const transportCheckedAt = new Date(metadata.chainTransportCheckedAt || 0);
  const transportValid = metadata.chainTransportSchema === 'plais-validator-transport-evidence/v1'
    && metadata.chainTransportMode === 'outbound-public-bridge'
    && isPublicIpv4(transportHost)
    && Number.isInteger(transportPort)
    && transportPort >= 1024
    && transportPort <= 65535
    && /^[a-f0-9]{64}$/.test(transportEnodeHash)
    && !Number.isNaN(transportCheckedAt.getTime())
    && Math.abs(Date.now() - transportCheckedAt.getTime()) <= 10 * 60 * 1000;
  metadata.chainTransportSchema = transportValid ? metadata.chainTransportSchema : '';
  metadata.chainTransportMode = transportValid ? metadata.chainTransportMode : '';
  metadata.chainTransportBridgeHost = transportValid ? transportHost : '';
  metadata.chainTransportBridgePort = transportValid ? transportPort : 0;
  metadata.chainTransportBridgeEnodeHash = transportValid ? transportEnodeHash : '';
  metadata.chainTransportReachable = transportValid && metadata.chainTransportReachable === true;
  metadata.chainTransportCheckedAt = transportValid ? transportCheckedAt.toISOString() : null;
  metadata.chainProvisioningSchema = metadata.chainProvisioningSchema === 'plais-node-provisioning/v1'
    ? metadata.chainProvisioningSchema
    : '';
  const provisioningHash = String(metadata.chainProvisioningHash || '').trim().toLowerCase();
  metadata.chainProvisioningHash = /^[a-f0-9]{64}$/.test(provisioningHash) ? provisioningHash : '';
  metadata.chainProvisioningRole = ['participant', 'validator-candidate'].includes(metadata.chainProvisioningRole)
    ? metadata.chainProvisioningRole
    : '';
  metadata.chainProvisioningState = String(metadata.chainProvisioningState || '').slice(0, 80);
  const provisioningReceivedAt = new Date(metadata.chainProvisioningReceivedAt || 0);
  metadata.chainProvisioningReceivedAt = metadata.chainProvisioningHash && !Number.isNaN(provisioningReceivedAt.getTime())
    ? provisioningReceivedAt.toISOString()
    : null;
  const provisioningKeyAlgorithm = String(metadata.provisioningKeyAlgorithm || '').trim();
  const provisioningKeyId = String(metadata.provisioningKeyId || '').trim().toLowerCase();
  const provisioningPublicKey = String(metadata.provisioningPublicKey || '').trim();
  let provisioningKeyValid = false;
  if (
    provisioningKeyAlgorithm === 'rsa-oaep-sha256-dpapi-current-user-v1' &&
    /^[a-f0-9]{64}$/.test(provisioningKeyId) &&
    provisioningPublicKey &&
    provisioningPublicKey.length <= 2048
  ) {
    try {
      const der = Buffer.from(provisioningPublicKey, 'base64');
      const parsed = crypto.createPublicKey({ key: der, format: 'der', type: 'spki' });
      const computedKeyId = crypto.createHash('sha256').update(der).digest('hex');
      provisioningKeyValid = parsed.asymmetricKeyType === 'rsa'
        && Number(parsed.asymmetricKeyDetails?.modulusLength || 0) >= 3072
        && computedKeyId === provisioningKeyId;
    } catch (error) {
      provisioningKeyValid = false;
    }
  }
  metadata.provisioningKeyAlgorithm = provisioningKeyValid ? provisioningKeyAlgorithm : '';
  metadata.provisioningKeyId = provisioningKeyValid ? provisioningKeyId : '';
  metadata.provisioningPublicKey = provisioningKeyValid ? provisioningPublicKey : '';
  const provisioningKeyRegisteredAt = new Date(metadata.provisioningKeyRegisteredAt || 0);
  metadata.provisioningKeyRegisteredAt = provisioningKeyValid && !Number.isNaN(provisioningKeyRegisteredAt.getTime())
    ? provisioningKeyRegisteredAt.toISOString()
    : null;
  metadata.validatorKeyDeliverySchema = metadata.validatorKeyDeliverySchema === 'plais-validator-key-delivery-offer/v1'
    ? metadata.validatorKeyDeliverySchema
    : '';
  const validatorEnvelopeHash = String(metadata.validatorKeyEnvelopeHash || '').trim().toLowerCase();
  metadata.validatorKeyEnvelopeHash = /^[a-f0-9]{64}$/.test(validatorEnvelopeHash) ? validatorEnvelopeHash : '';
  metadata.validatorKeyDeliveryState = ['locked-awaiting-delivery-approval', 'approved-awaiting-encrypted-download', 'encrypted-envelope-staged']
    .includes(metadata.validatorKeyDeliveryState)
    ? metadata.validatorKeyDeliveryState
    : '';
  const validatorKeyDeliveryReceivedAt = new Date(metadata.validatorKeyDeliveryReceivedAt || 0);
  metadata.validatorKeyDeliveryReceivedAt = metadata.validatorKeyEnvelopeHash && !Number.isNaN(validatorKeyDeliveryReceivedAt.getTime())
    ? validatorKeyDeliveryReceivedAt.toISOString()
    : null;
  const runtimeSha256 = String(metadata.validatorRuntimeArtifactSha256 || '').trim().toLowerCase();
  const runtimeDeviceKeyId = String(metadata.validatorRuntimeDeviceKeyId || '').trim().toLowerCase();
  const runtimeCheckedAt = new Date(metadata.validatorRuntimeCheckedAt || 0);
  const runtimeStates = ['locked-awaiting-runtime-approval', 'approved-awaiting-runtime-presync', 'runtime-downloading', 'runtime-installed', 'presync-starting', 'presync-syncing', 'presync-ready', 'validator-activating', 'validator-active', 'runtime-error'];
  const runtimeValid = metadata.validatorRuntimeSchema === 'plais-validator-runtime-deployment-status/v1'
    && /^[a-f0-9]{64}$/.test(runtimeSha256)
    && runtimeDeviceKeyId === metadata.provisioningKeyId
    && runtimeStates.includes(metadata.validatorRuntimeState)
    && !Number.isNaN(runtimeCheckedAt.getTime())
    && Math.abs(Date.now() - runtimeCheckedAt.getTime()) <= 10 * 60 * 1000;
  metadata.validatorRuntimeSchema = runtimeValid ? metadata.validatorRuntimeSchema : '';
  metadata.validatorRuntimeVersion = runtimeValid ? String(metadata.validatorRuntimeVersion || '').slice(0, 120) : '';
  metadata.validatorRuntimeArtifactSha256 = runtimeValid ? runtimeSha256 : '';
  metadata.validatorRuntimeState = runtimeValid ? metadata.validatorRuntimeState : '';
  metadata.validatorRuntimeDeviceKeyId = runtimeValid ? runtimeDeviceKeyId : '';
  metadata.validatorRuntimeApprovalId = runtimeValid ? String(metadata.validatorRuntimeApprovalId || '').slice(0, 128) : '';
  metadata.validatorRuntimeProcessPid = runtimeValid ? Math.max(0, Number(metadata.validatorRuntimeProcessPid || 0)) : 0;
  metadata.validatorRuntimeBlockNumber = runtimeValid ? Math.max(0, Number(metadata.validatorRuntimeBlockNumber || 0)) : 0;
  metadata.validatorRuntimeCanonicalBlock = runtimeValid ? Math.max(0, Number(metadata.validatorRuntimeCanonicalBlock || 0)) : 0;
  metadata.validatorRuntimeGapBlocks = runtimeValid ? Math.max(0, Number(metadata.validatorRuntimeGapBlocks || 0)) : 0;
  metadata.validatorRuntimePeerCount = runtimeValid ? Math.max(0, Number(metadata.validatorRuntimePeerCount || 0)) : 0;
  metadata.validatorRuntimeCheckedAt = runtimeValid ? runtimeCheckedAt.toISOString() : null;
  const tunnelTopologyHash = String(metadata.validatorTunnelTopologyHash || '').trim().toLowerCase();
  const tunnelAppliedAt = new Date(metadata.validatorTunnelAppliedAt || 0);
  const tunnelValid = runtimeValid
    && metadata.validatorTunnelSchema === 'plais-validator-p2p-tunnel/v1'
    && ['direct-peers-applied', 'direct-lan-quorum-with-tunnel-fallback-applied']
      .includes(metadata.validatorTunnelState)
    && /^[a-f0-9]{64}$/.test(tunnelTopologyHash)
    && !Number.isNaN(tunnelAppliedAt.getTime());
  metadata.validatorTunnelSchema = tunnelValid ? metadata.validatorTunnelSchema : '';
  metadata.validatorTunnelState = tunnelValid ? metadata.validatorTunnelState : '';
  metadata.validatorTunnelTopologyHash = tunnelValid ? tunnelTopologyHash : '';
  metadata.validatorTunnelApprovalId = tunnelValid ? String(metadata.validatorTunnelApprovalId || '').slice(0, 128) : '';
  metadata.validatorTunnelAppliedAt = tunnelValid ? tunnelAppliedAt.toISOString() : null;
  const inventoryCheckedAt = new Date(metadata.resilienceInventoryCheckedAt || 0);
  const inventoryCollectorSha256 = String(metadata.resilienceInventoryCollectorSha256 || '').trim().toLowerCase();
  const logicalCpuCount = Number(metadata.resilienceLogicalCpuCount || 0);
  const totalMemoryBytes = Number(metadata.resilienceTotalMemoryBytes || 0);
  const freeMemoryBytes = Number(metadata.resilienceFreeMemoryBytes || 0);
  const hostUptimeSeconds = Number(metadata.resilienceHostUptimeSeconds || 0);
  const nodeVolumeRoot = String(metadata.resilienceNodeVolumeRoot || '').trim().toUpperCase();
  const nodeVolumeTotalBytes = Number(metadata.resilienceNodeVolumeTotalBytes || 0);
  const nodeVolumeFreeBytes = Number(metadata.resilienceNodeVolumeFreeBytes || 0);
  const nodeVolumeStatus = String(metadata.resilienceNodeVolumeStatus || '').trim();
  const resilienceInventoryValid = metadata.resilienceInventorySchema === 'plai-resilience-node-inventory/v1'
    && /^[a-f0-9]{64}$/.test(inventoryCollectorSha256)
    && !Number.isNaN(inventoryCheckedAt.getTime())
    && Math.abs(Date.now() - inventoryCheckedAt.getTime()) <= 10 * 60 * 1000
    && Number.isInteger(logicalCpuCount) && logicalCpuCount >= 1 && logicalCpuCount <= 4096
    && Number.isFinite(totalMemoryBytes) && totalMemoryBytes >= 256 * 1024 * 1024
    && Number.isFinite(freeMemoryBytes) && freeMemoryBytes >= 0 && freeMemoryBytes <= totalMemoryBytes
    && Number.isInteger(hostUptimeSeconds) && hostUptimeSeconds >= 0
    && /^[A-Z]:\\$/.test(nodeVolumeRoot)
    && Number.isFinite(nodeVolumeTotalBytes) && nodeVolumeTotalBytes > 0
    && Number.isFinite(nodeVolumeFreeBytes) && nodeVolumeFreeBytes >= 0 && nodeVolumeFreeBytes <= nodeVolumeTotalBytes
    && ['CAPACITY_READ_ONLY', 'UNKNOWN_UNAVAILABLE'].includes(nodeVolumeStatus)
    && metadata.resilienceStorageHealth === 'UNKNOWN_NON_ADMIN';
  metadata.resilienceInventorySchema = resilienceInventoryValid ? metadata.resilienceInventorySchema : '';
  metadata.resilienceInventoryCheckedAt = resilienceInventoryValid ? inventoryCheckedAt.toISOString() : null;
  metadata.resilienceInventoryCollectorSha256 = resilienceInventoryValid ? inventoryCollectorSha256 : '';
  metadata.resilienceLogicalCpuCount = resilienceInventoryValid ? logicalCpuCount : 0;
  metadata.resilienceTotalMemoryBytes = resilienceInventoryValid ? totalMemoryBytes : 0;
  metadata.resilienceFreeMemoryBytes = resilienceInventoryValid ? freeMemoryBytes : 0;
  metadata.resilienceHostUptimeSeconds = resilienceInventoryValid ? hostUptimeSeconds : 0;
  metadata.resilienceNodeVolumeRoot = resilienceInventoryValid ? nodeVolumeRoot : '';
  metadata.resilienceNodeVolumeTotalBytes = resilienceInventoryValid ? nodeVolumeTotalBytes : 0;
  metadata.resilienceNodeVolumeFreeBytes = resilienceInventoryValid ? nodeVolumeFreeBytes : 0;
  metadata.resilienceNodeVolumeStatus = resilienceInventoryValid ? nodeVolumeStatus : '';
  metadata.resilienceStorageHealth = resilienceInventoryValid ? metadata.resilienceStorageHealth : '';
  delete metadata.networkReportedAt;
  delete metadata.publicEgressReportedAt;
  return { ...payload, metadata };
}

function save() {
  state.nodes = nodes;
  state.nodeEconomyEntries = economyEntries;
  persistentStore.writeState(state);
}

function seedDefaultNodes() {
  if (nodes.length > 0) return;

  defaultNodes.forEach((payload) => {
    nodes.push(new NodeParticipant({
      ...payload,
      deviceName: `${payload.serviceName} mock participant`,
      metadata: { seeded: true }
    }));
  });
  save();
}

function listNodes() {
  seedDefaultNodes();
  return nodes;
}

function getNode(nodeId) {
  return listNodes().find((node) => node.nodeId === nodeId || node.id === nodeId);
}

function readCanonicalMigrationTopology() {
  try {
    if (!fs.existsSync(canonicalMigrationTopologyPath)) return null;
    const topology = JSON.parse(fs.readFileSync(canonicalMigrationTopologyPath, 'utf8'));
    return topology && typeof topology === 'object' ? topology : null;
  } catch (error) {
    return null;
  }
}

function metadataProvisioningKeyReady(metadata = {}) {
  return metadata.provisioningKeyAlgorithm === 'rsa-oaep-sha256-dpapi-current-user-v1'
    && /^[a-f0-9]{64}$/.test(String(metadata.provisioningKeyId || ''))
    && Boolean(metadata.provisioningPublicKey);
}

function getNodeProvisioning(nodeId) {
  const node = getNode(nodeId);
  if (!node) return null;

  const topology = readCanonicalMigrationTopology();
  const pcName = String(node.pcName || node.metadata?.hostname || '').trim();
  const target = findValidatorTarget(topology, node);
  const issuedAt = new Date();
  const validatorCandidate = Boolean(target);
  const provisioningKeyReady = metadataProvisioningKeyReady(node.metadata || {});
  let transport = {
    mode: 'pending-automatic-selection',
    manualVpnRequired: false,
    manualAddressEntryRequired: false
  };
  if (validatorCandidate) {
    try {
      const bridge = JSON.parse(fs.readFileSync(publicBridgeConfigPath, 'utf8'));
      const bridgePublicKey = fs.readFileSync(publicBridgeKeyPath, 'utf8').trim().replace(/^0x/, '');
      const bridgePort = Number(bridge.p2pPort || 0);
      const observedEgress = String(node.metadata?.publicEgressAddress || '').trim();
      const allowedRemoteAddresses = Array.isArray(bridge.allowedRemoteAddresses)
        ? bridge.allowedRemoteAddresses.map((value) => String(value || '').trim())
        : [];
      if (
        bridge.schemaVersion === 'plais-qbft-public-bridge/v1'
        && bridge.enabled === true
        && isPublicIpv4(bridge.advertisedHost)
        && Number.isInteger(bridgePort)
        && bridgePort >= 1024
        && bridgePort <= 65535
        && /^[a-f0-9]{128}$/i.test(bridgePublicKey)
      ) {
        transport = {
          mode: 'outbound-public-bridge',
          bridgeHost: bridge.advertisedHost,
          bridgePort,
          bridgeEnode: `enode://${bridgePublicKey}@${bridge.advertisedHost}:${bridgePort}`,
          outboundOnly: true,
          validatorInboundPortRequired: false,
          sourceAddressAllowlisted: allowedRemoteAddresses.includes(observedEgress),
          manualVpnRequired: false,
          manualAddressEntryRequired: false
        };
      }
    } catch (error) {
      transport = {
        ...transport,
        state: 'bridge-not-ready'
      };
    }
  }

  return {
    schemaVersion: 'plais-node-provisioning/v1',
    deliveryMode: 'aisfor-sync-client',
    nodeId: String(node.nodeId || node.id || ''),
    pcName,
    network: {
      name: EVM_CANDIDATE_CONFIG.networkName,
      chainId: EVM_CANDIDATE_CONFIG.chainId,
      consensus: EVM_CANDIDATE_CONFIG.consensus,
      p2pPort: Number(topology?.p2pPort || 30303),
      publicRpcAllowed: false
    },
    genesisSha256: String(topology?.genesisSha256 || ''),
    assignment: {
      role: validatorCandidate ? 'validator-candidate' : 'participant',
      targetNodeName: validatorCandidate ? String(target.nodeName || '') : '',
      state: validatorCandidate ? 'awaiting-operator-approval' : 'registered'
    },
    transport,
    encryption: {
      requiredAlgorithm: 'rsa-oaep-sha256-dpapi-current-user-v1',
      deviceKeyReady: provisioningKeyReady,
      deviceKeyId: provisioningKeyReady ? String(node.metadata.provisioningKeyId || '') : ''
    },
    controls: {
      arbitraryCommandAllowed: false,
      packageInstallAllowed: false,
      validatorKeyImportAllowed: false,
      validatorStartAllowed: false
    },
    migrationState: String(topology?.state || 'NOT_CONFIGURED'),
    issuedAt: issuedAt.toISOString(),
    expiresAt: new Date(issuedAt.getTime() + 10 * 60 * 1000).toISOString()
  };
}

function registerNode(payload = {}) {
  seedDefaultNodes();
  const safePayload = sanitizeNodeRegistration(payload);
  const existing = safePayload.nodeId ? getNode(safePayload.nodeId) : null;

  if (existing) {
    Object.assign(existing, new NodeParticipant({ ...existing, ...safePayload, id: existing.id, nodeId: existing.nodeId }));
    save();
    return { node: existing, created: false };
  }

  const node = new NodeParticipant({ ...safePayload, status: safePayload.status || 'pending' });
  nodes.unshift(node);
  save();
  return { node, created: true };
}

function heartbeat(nodeId, payload = {}) {
  const node = getNode(nodeId);
  if (!node) return null;

  if (typeof node.applyHeartbeat === 'function') {
    node.applyHeartbeat(payload);
  } else {
    Object.assign(node, new NodeParticipant(node));
    node.applyHeartbeat(payload);
  }

  save();
  return node;
}

function updateStatus(nodeId, status) {
  const node = getNode(nodeId);
  if (!node || !NodeParticipant.VALID_STATUSES.has(status)) return null;

  node.status = status;
  if (status === 'exited') node.exitedAt = new Date().toISOString();
  if (status === 'warning') node.warningCount = Number(node.warningCount || 0) + 1;
  save();
  return node;
}

async function recordEconomyEvent(node, {
  eventType,
  amount,
  reason,
  refType,
  refId,
  metadata
} = {}) {
  const value = Number(amount || 0);
  const transaction = await chainTransactionService.submitTransaction({
    sourceService: 'AISFOR_CHAIN',
    actionType: eventType,
    actorId: node.userId || node.userIdHash || node.nodeId,
    targetId: node.nodeId,
    amount: value,
    metadata: {
      nodeId: node.nodeId,
      serviceName: node.serviceName,
      reason: reason || '',
      refType: refType || null,
      refId: refId || null,
      ...metadata
    }
  });

  const entry = new NodeEconomyLedger({
    nodeId: node.nodeId,
    userId: node.userId || node.userIdHash || null,
    sourceService: node.serviceName || 'AISFOR_CHAIN',
    eventType,
    amount: value,
    balanceLocked: node.lockedPoint,
    balanceReward: node.rewardPoint,
    balanceSlashed: node.slashPoint,
    reason,
    refType,
    refId,
    transactionId: transaction.id,
    txHash: transaction.txHash,
    metadata
  });

  economyEntries.unshift(entry);

  if (eventType === 'NODE_REWARD' || eventType === 'NODE_SLASH') {
    pointLedgerService.createEntry({
      actorId: node.userId || node.userIdHash || node.nodeId,
      sourceService: node.serviceName || 'AISFOR_CHAIN',
      reason: eventType,
      points: eventType === 'NODE_SLASH' ? -Math.abs(value) : value,
      refType: 'node',
      refId: node.nodeId,
      metadata: {
        economyEntryId: entry.id,
        transactionId: transaction.id,
        txHash: transaction.txHash,
        reason: reason || ''
      }
    });
  }

  save();

  return {
    node,
    entry,
    transaction
  };
}

async function lockPledge(nodeId, amount, reason = 'node pledge locked') {
  const node = getNode(nodeId);
  if (!node) return null;

  const value = Math.max(0, Number(amount || 0));
  node.pledgedPoint = Number(node.pledgedPoint || 0) + value;
  node.lockedPoint = Number(node.lockedPoint || 0) + value;

  return recordEconomyEvent(node, {
    eventType: 'NODE_PLEDGE_LOCKED',
    amount: value,
    reason
  });
}

async function rewardNode(nodeId, amount, reason = 'node reward', metadata = {}) {
  const node = getNode(nodeId);
  if (!node) return null;

  const value = Math.max(0, Number(amount || 0));
  node.rewardPoint = Number(node.rewardPoint || 0) + value;

  return recordEconomyEvent(node, {
    eventType: 'NODE_REWARD',
    amount: value,
    reason,
    metadata
  });
}

async function slashNode(nodeId, amount, reason = 'node slash', metadata = {}) {
  const node = getNode(nodeId);
  if (!node) return null;

  const value = Math.max(0, Number(amount || 0));
  node.slashPoint = Number(node.slashPoint || 0) + value;
  node.lockedPoint = Math.max(0, Number(node.lockedPoint || 0) - value);
  node.slashCount = Number(node.slashCount || 0) + 1;
  node.status = 'slashed';
  node.metadata = { ...(node.metadata || {}), lastSlashReason: reason || 'unspecified' };

  return recordEconomyEvent(node, {
    eventType: 'NODE_SLASH',
    amount: value,
    reason,
    metadata
  });
}

function listEconomyEntries() {
  return economyEntries;
}

function getEconomyStats() {
  return nodes.reduce((acc, node) => {
    acc.totalPledged += Number(node.pledgedPoint || 0);
    acc.totalLocked += Number(node.lockedPoint || 0);
    acc.totalRewarded += Number(node.rewardPoint || 0);
    acc.totalSlashed += Number(node.slashPoint || 0);
    return acc;
  }, {
    totalPledged: 0,
    totalLocked: 0,
    totalRewarded: 0,
    totalSlashed: 0,
    totalEntries: economyEntries.length
  });
}

module.exports = {
  listNodes,
  getNode,
  getNodeProvisioning,
  registerNode,
  heartbeat,
  updateStatus,
  lockPledge,
  rewardNode,
  slashNode,
  listEconomyEntries,
  getEconomyStats
};
