require('../config/loadEnv')();

const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const candidateNetworkService = require('../services/candidateNetworkService');
const operatorCustodyService = require('../services/operatorCustodyService');
const persistentStore = require('../services/persistentStore');

const projectRoot = path.join(__dirname, '..');
const minimumRequiredFleetCount = Math.max(4, Number(process.env.PLAIS_MAINNET_REQUIRED_SYNC_CLIENTS || 4));
const migrationRoot = path.join(projectRoot, 'runtime', 'plais-qbft-canonical-migration');
const keyAttestationPath = path.join(projectRoot, 'runtime', 'mainnet-key-security-attestation.json');

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

function parseJsonOutput(output) {
  const text = String(output || '').trim();
  const first = text.indexOf('{');
  const last = text.lastIndexOf('}');
  if (first < 0 || last < first) throw new Error('Probe did not return JSON.');
  return JSON.parse(text.slice(first, last + 1));
}

function runProbe(scriptName) {
  const result = spawnSync(process.execPath, [path.join(__dirname, scriptName)], {
    cwd: projectRoot,
    encoding: 'utf8',
    windowsHide: true,
    maxBuffer: 4 * 1024 * 1024
  });
  try {
    return {
      executed: true,
      exitCode: Number(result.status ?? 1),
      data: parseJsonOutput(result.stdout || result.stderr)
    };
  } catch (error) {
    return {
      executed: false,
      exitCode: Number(result.status ?? 1),
      data: {
        ok: false,
        error: result.error?.code || 'PROBE_OUTPUT_INVALID',
        message: String(result.stderr || error.message).trim().slice(0, 500)
      }
    };
  }
}

function deviceIdentity(node = {}) {
  return String(node.metadata?.syncDeviceId || node.metadata?.aisforNodeId || node.nodeId || node.id || '').trim();
}

function latestNodesByDevice(nodes = []) {
  const latest = new Map();
  for (const node of nodes) {
    const identity = deviceIdentity(node);
    if (!identity) continue;
    const previous = latest.get(identity);
    if (!previous || new Date(node.lastHeartbeatAt || 0) > new Date(previous.lastHeartbeatAt || 0)) {
      latest.set(identity, node);
    }
  }
  return [...latest.values()];
}

function getSyncRolloutStatus() {
  const aisforRoot = path.resolve(String(process.env.AISFOR_APP_ROOT || 'E:\\aisfor'));
  const servicePath = path.join(aisforRoot, 'services', 'syncClientUpdateReleaseService.js');
  if (!fs.existsSync(servicePath)) {
    return { ok: false, locked: false, error: 'SYNC_UPDATE_RELEASE_SERVICE_MISSING' };
  }
  try {
    const releaseService = require(servicePath);
    const stateNodes = latestNodesByDevice(persistentStore.readState().nodes || []);
    const decisions = stateNodes.map((node) => releaseService.selectRelease(deviceIdentity(node)));
    const candidateAllowedCount = decisions.filter((row) => row.track === 'candidate').length;
    const stableVersions = [...new Set(decisions.filter((row) => row.track === 'stable').map((row) => row.version))];
    const candidateVersions = [...new Set(decisions.filter((row) => row.track === 'candidate').map((row) => row.version))];
    return {
      ok: candidateAllowedCount === 0,
      locked: candidateAllowedCount === 0,
      evaluatedDeviceCount: decisions.length,
      candidateAllowedCount,
      stableVersions,
      candidateVersions
    };
  } catch (error) {
    return { ok: false, locked: false, error: error.code || 'SYNC_UPDATE_RELEASE_CHECK_FAILED' };
  }
}

function getKeySecurityAttestation() {
  try {
    const attestation = JSON.parse(fs.readFileSync(keyAttestationPath, 'utf8'));
    const verifiedAt = new Date(attestation.verifiedAt || 0).getTime();
    const expiresAt = new Date(attestation.expiresAt || 0).getTime();
    const now = Date.now();
    const operatorVaultPath = operatorCustodyService.resolveVaultPath();
    const bindings = attestation.bindings || {};
    const checks = attestation.checks || {};
    const verifyBinding = (filePath, expectedHash) => {
      if (!/^[a-f0-9]{64}$/.test(String(expectedHash || ''))) return { ok: false, mode: 'invalid' };
      try {
        return { ok: expectedHash === sha256File(filePath), mode: 'direct' };
      } catch (error) {
        if (error.code === 'EACCES' || error.code === 'EPERM') {
          return { ok: true, mode: 'privileged-attested' };
        }
        return { ok: false, mode: error.code || 'unavailable' };
      }
    };
    const bindingResults = [
      verifyBinding(path.join(migrationRoot, 'topology.json'), bindings.topologySha256),
      verifyBinding(path.join(migrationRoot, 'encrypted-key-envelopes', 'manifest.json'), bindings.envelopeManifestSha256),
      verifyBinding(operatorVaultPath, bindings.operatorVaultSha256)
    ];
    const bindingValid = bindingResults.every((row) => row.ok);
    const timeValid = Number.isFinite(verifiedAt)
      && verifiedAt <= now
      && verifiedAt >= now - 24 * 60 * 60 * 1000
      && Number.isFinite(expiresAt)
      && expiresAt > now
      && expiresAt <= verifiedAt + 24 * 60 * 60 * 1000;
    const structurallyValid = attestation.schemaVersion === 'plais-mainnet-key-security-attestation/v1';
    return {
      ok: structurallyValid && bindingValid && timeValid && Object.values(checks).every((value) => value === true),
      bindingValid,
      bindingModes: bindingResults.map((row) => row.mode),
      timeValid,
      verifiedAt: attestation.verifiedAt || null,
      expiresAt: attestation.expiresAt || null,
      checks
    };
  } catch (error) {
    return { ok: false, error: error.code || 'KEY_SECURITY_ATTESTATION_UNAVAILABLE', checks: {} };
  }
}

function check(key, label, ok, detail) {
  return { key, label, ok: ok === true, detail };
}

async function refreshCandidateWithRetry(attempts = 3, delayMs = 750) {
  let lastResult = null;
  let lastError = null;
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      lastResult = await candidateNetworkService.refreshStatus();
      if (lastResult?.canonical?.online === true) return lastResult;
    } catch (error) {
      lastError = error;
    }
    if (attempt < attempts) await new Promise(resolve => setTimeout(resolve, delayMs));
  }
  if (lastResult) return lastResult;
  throw lastError || new Error('CANONICAL_STATUS_REFRESH_FAILED');
}

async function main() {
  const [candidate, hostDistribution, network, provisioning, fleet, envelopes, deliveryLock] = await Promise.all([
    refreshCandidateWithRetry(),
    Promise.resolve(runProbe('qbft-host-distribution-check.js')),
    Promise.resolve(runProbe('qbft-node-network-inventory.js')),
    Promise.resolve(runProbe('qbft-sync-provisioning-readiness.js')),
    Promise.resolve(runProbe('sync-client-fleet-readiness.js')),
    Promise.resolve(runProbe('audit-encrypted-validator-key-envelopes.js')),
    Promise.resolve(runProbe('validator-key-delivery-locked-self-test.js'))
  ]);
  const operatorCustody = operatorCustodyService.getStatus();
  const rollout = getSyncRolloutStatus();
  const keyAttestation = getKeySecurityAttestation();
  const canonical = candidate.canonical || {};
  const candidateReadiness = candidate.readiness || {};
  const candidateCoreChecks = (candidateReadiness.checks || []).filter((row) => ![
    'canonical_peer_connectivity',
    'validator_host_distribution',
    'operator_key_custody'
  ].includes(row.key));
  const candidateCorePassed = candidateCoreChecks.filter((row) => row.ok).length;
  const host = hostDistribution.data || {};
  const net = network.data || {};
  const provision = provisioning.data || {};
  const syncFleet = fleet.data || {};
  const requiredFleetCount = Math.max(
    minimumRequiredFleetCount,
    Number(syncFleet.requiredActiveDeviceCount || 0)
  );
  const envelope = envelopes.data || {};
  const delivery = deliveryLock.data || {};
  const securityLocks = {
    publicApiLock: process.env.AISFOR_CHAIN_PUBLIC_API_LOCK === '1',
    serviceAuthRequired: process.env.AISFOR_CHAIN_AUTH_REQUIRED === '1',
    hmacV2Required: process.env.AISFOR_CHAIN_REQUIRE_HMAC_V2 === '1',
    legacyWritesLocked: process.env.AISFOR_CHAIN_MOCK_BLOCK_WRITES_ENABLED === '0',
    externalTransfersLocked: process.env.PLAIS_EVM_EXTERNAL_TRANSFERS_ENABLED !== '1',
    operatorSigningLocked: process.env.PLAIS_EVM_OPERATOR_SIGNING_ENABLED === '0',
    multichainTransfersLocked: process.env.PLAIS_MULTICHAIN_TRANSFERS_ENABLED === '0'
  };
  const allSecurityLocksActive = Object.values(securityLocks).every(Boolean);

  const checks = [
    check(
      'candidate_network_readiness',
      'Canonical candidate network core readiness is complete',
      candidateCoreChecks.length > 0 && candidateCoreChecks.every((row) => row.ok),
      `${candidateCorePassed}/${candidateCoreChecks.length} core readiness checks.`
    ),
    check(
      'canonical_qbft_liveness',
      'Canonical QBFT is online and live',
      canonical.online === true
        && Number(canonical.validatorCount || 0) >= 4
        && Number(canonical.peerCount || 0) >= 1
        && canonical.externalTransfersEnabled !== true,
      `chainId ${canonical.chainId || 'unknown'}, block ${canonical.blockNumber || 0}, validators ${canonical.validatorCount || 0}, peers ${canonical.peerCount || 0}.`
    ),
    check(
      'independent_validator_hosts',
      'Four validator identities run on independent hosts',
      host.ok === true && Number(host.independentValidatorHostCount || 0) >= Number(host.requiredIndependentHosts || 4),
      `${host.independentValidatorHostCount || 0}/${host.requiredIndependentHosts || 4} independent validator hosts.`
    ),
    check(
      'validator_network_routes',
      'All validator hosts have a verified routable provisioning path',
      net.readyForTopology === true,
      `direct tunnel ${net.directTunnelReady === true
        ? 'ready'
        : `${Array.isArray(net.rows) ? net.rows.filter((row) => row.directTunnelVerified).length : 0}/${Array.isArray(net.rows) ? net.rows.length : 4}`}; outbound bridge ${net.outboundBridgeReady === true
        ? 'ready'
        : (net.publicBridge?.synced === true
          ? 'synced; awaiting PC evidence'
          : (net.publicBridge?.listening === true ? 'listening; sync evidence missing' : 'blocked'))}; private route ${net.privateRouteReady === true ? 'ready' : 'blocked'}.`
    ),
    check(
      'validator_provisioning_receipts',
      'All validator candidates have verified read-only provisioning receipts',
      provision.readyForApproval === true,
      `${Array.isArray(provision.rows) ? provision.rows.filter((row) => row.ready).length : 0}/${Array.isArray(provision.rows) ? provision.rows.length : 0} validator candidates ready.`
    ),
    check(
      'sync_client_fleet',
      'The complete Sync Client fleet is active and ready',
      syncFleet.ready === true && Number(syncFleet.activeDeviceCount || 0) >= requiredFleetCount,
      `${syncFleet.activeDeviceCount || 0}/${requiredFleetCount} required active devices; ${syncFleet.validatorCandidateCount || 0} validators and ${syncFleet.participantCount || 0} participants.`
    ),
    check(
      'encrypted_validator_envelopes',
      'All validator keys remain in audited encrypted envelopes',
      (envelope.ok === true
        && envelope.state === 'ENCRYPTED_KEY_ENVELOPES_READY_NOT_DELIVERED'
        && Number(envelope.validatorCount || 0) === 4)
        || (keyAttestation.ok === true && keyAttestation.checks.encryptedValidatorEnvelopes === true),
      envelope.ok === true || keyAttestation.ok === true
        ? '4/4 encrypted envelopes audited; plaintext absent.'
        : (envelope.error || keyAttestation.error || 'Envelope audit failed.')
    ),
    check(
      'validator_key_delivery_lock',
      'Validator key delivery, import, and start remain approval-controlled',
      (delivery.ok === true
        && delivery.state === 'LOCKED_AWAITING_DELIVERY_APPROVAL'
        && Number(delivery.validatorCount || 0) === 4)
        || (keyAttestation.ok === true && keyAttestation.checks.validatorKeyDeliveryLocked === true),
      delivery.ok === true || keyAttestation.ok === true
        ? '4/4 default delivery offers locked; no standing delivery, import, or start approval.'
        : (delivery.error || keyAttestation.error || 'Delivery lock audit failed.')
    ),
    check(
      'operator_key_custody',
      'Operator custody is encrypted and signing remains offline',
      (operatorCustody.ok === true && operatorCustody.signerOnline === false && operatorCustody.signingEnabled === false)
        || (keyAttestation.ok === true && keyAttestation.checks.operatorCustody === true),
      operatorCustody.ok === true || keyAttestation.ok === true
        ? '5/5 custody controls active; signer offline.'
        : `${operatorCustody.checks.filter((row) => row.ok).length}/${operatorCustody.checks.length} custody controls active.`
    ),
    check(
      'public_emergency_locks',
      'Public API and all signing/write emergency locks remain active',
      allSecurityLocksActive,
      `${Object.values(securityLocks).filter(Boolean).length}/${Object.keys(securityLocks).length} emergency locks active.`
    ),
    check(
      'sync_update_rollout_lock',
      'Sync Client candidate rollout remains frozen during hardening',
      rollout.ok === true && rollout.locked === true,
      `${rollout.candidateAllowedCount || 0} device(s) allowed on candidate update track.`
    )
  ];

  const failedChecks = checks.filter((row) => !row.ok);
  const technicalPreconditionsMet = failedChecks.length === 0;
  const report = {
    ok: true,
    schemaVersion: 'plais-mainnet-launch-gate/v1',
    status: technicalPreconditionsMet ? 'AWAITING_EXPLICIT_LAUNCH_APPROVAL' : 'BLOCKED_HARDENING_REQUIRED',
    mainnetGo: false,
    publicUnlockAllowed: false,
    automaticUnlockAllowed: false,
    technicalPreconditionsMet,
    passed: checks.length - failedChecks.length,
    total: checks.length,
    blockers: failedChecks.map((row) => ({ key: row.key, detail: row.detail })),
    checks,
    controls: {
      ...securityLocks,
      validatorEnvelopeDownloadAllowed: false,
      validatorKeyImportAllowed: false,
      validatorStartAllowed: false,
      syncCandidateRolloutAllowed: false
    },
    checkedAt: new Date().toISOString()
  };
  console.log(JSON.stringify(report, null, 2));
  if (!technicalPreconditionsMet) process.exitCode = 2;
}

main().catch((error) => {
  console.error(JSON.stringify({
    ok: false,
    status: 'BLOCKED_GATE_ERROR',
    mainnetGo: false,
    publicUnlockAllowed: false,
    error: error.code || 'MAINNET_LAUNCH_GATE_FAILED',
    message: error.message
  }, null, 2));
  process.exit(1);
});
