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

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const projectRoot = path.join(__dirname, '..');
const reportPath = path.join(projectRoot, 'docs', 'listing', '05-security-audit', 'evidence', 'internal-preaudit-report.json');

function parseJsonOutput(output) {
  const value = String(output || '').trim();
  const first = value.indexOf('{');
  const last = value.lastIndexOf('}');
  if (first < 0 || last < first) return null;
  try {
    return JSON.parse(value.slice(first, last + 1));
  } catch (_error) {
    return null;
  }
}

function runNodeScript(relativePath) {
  const result = spawnSync(process.execPath, [path.join(projectRoot, relativePath)], {
    cwd: projectRoot,
    encoding: 'utf8',
    windowsHide: true,
    maxBuffer: 8 * 1024 * 1024
  });
  const parsed = parseJsonOutput(`${result.stdout || ''}\n${result.stderr || ''}`);
  return {
    ok: result.status === 0 && parsed?.ok === true,
    exitCode: result.status,
    result: parsed,
    error: parsed ? null : String(result.stderr || result.stdout || 'Unreadable audit output').trim().slice(0, 500)
  };
}

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

function publicSourceIntegrity() {
  try {
    const manifestPath = path.join(projectRoot, 'public', 'source-release', 'manifest.json');
    const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8').replace(/^\uFEFF/, ''));
    const packagePath = path.join(projectRoot, 'public', 'source-release', manifest.latest);
    const actualHash = fs.existsSync(packagePath) ? sha256File(packagePath) : null;
    const policy = manifest.publicationPolicy || {};
    const excluded = manifest.excluded || {};
    const docsListingExcluded = Array.isArray(policy.excludedPathPrefixes)
      && policy.excludedPathPrefixes.some((entry) => String(entry).replace(/\//g, '\\').toLowerCase() === 'docs\\listing');
    const currentTargets = [
      'package.json',
      'package-lock.json',
      'config/exchangeIntegration.example.json',
      'scripts/exchange-integration-readiness.js',
      'scripts/exchange-listing-readiness-01.js',
      'scripts/security-preaudit.js',
      'scripts/multichain-dependency-compatibility-self-test.js'
    ];
    const newestSourceMtime = Math.max(...currentTargets.map((relativePath) => {
      const targetPath = path.join(projectRoot, relativePath);
      return fs.existsSync(targetPath) ? fs.statSync(targetPath).mtimeMs : 0;
    }));
    const generatedAt = new Date(manifest.generatedAt || 0).getTime();
    const checks = {
      manifestValid: manifest.ok === true && manifest.fileCount > 0 && /^[a-f0-9]{64}$/i.test(String(manifest.sha256 || '')),
      latestPackageHashMatches: actualHash !== null && actualHash === manifest.latestSha256 && actualHash === manifest.sha256,
      allowlistPublication: policy.mode === 'allowlist',
      credentialPatternScanRecorded: policy.credentialPatternScan === true,
      localEnvironmentValueScanRecorded: policy.localEnvironmentValueScan === true,
      currentSourceSnapshot: Number.isFinite(generatedAt) && generatedAt >= newestSourceMtime,
      privateListingDataExcluded: docsListingExcluded,
      runtimeAndKeyMaterialExcluded: Array.isArray(excluded.directories)
        && excluded.directories.includes('runtime')
        && Array.isArray(excluded.patterns)
        && excluded.patterns.some((entry) => /validator keys\/envelopes/i.test(String(entry)))
    };
    return {
      ok: Object.values(checks).every(Boolean),
      version: manifest.version,
      package: manifest.latest,
      sha256: actualHash,
      checks
    };
  } catch (error) {
    return { ok: false, error: error.message };
  }
}

function dependencyAudit() {
  const npmCliPath = process.platform === 'win32'
    ? path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js')
    : null;
  const command = npmCliPath && fs.existsSync(npmCliPath) ? process.execPath : 'npm';
  const args = npmCliPath && fs.existsSync(npmCliPath)
    ? [npmCliPath, 'audit', '--omit=dev', '--json']
    : ['audit', '--omit=dev', '--json'];
  const result = spawnSync(command, args, {
    cwd: projectRoot,
    encoding: 'utf8',
    windowsHide: true,
    maxBuffer: 16 * 1024 * 1024,
    timeout: 120000
  });
  const parsed = parseJsonOutput(`${result.stdout || ''}\n${result.stderr || ''}`);
  if (!parsed || !parsed.metadata?.vulnerabilities) {
    return {
      ok: false,
      auditAvailable: false,
      error: String(result.stderr || result.error?.message || 'npm audit did not return readable JSON').trim().slice(0, 500)
    };
  }
  const counts = parsed.metadata.vulnerabilities;
  const packages = Object.entries(parsed.vulnerabilities || {}).map(([name, value]) => ({
    package: name,
    severity: value.severity,
    direct: value.isDirect === true,
    fixAvailable: Boolean(value.fixAvailable)
  }));
  return {
    ok: Number(counts.high || 0) === 0 && Number(counts.critical || 0) === 0,
    auditAvailable: true,
    exitCode: result.status,
    counts,
    packages
  };
}

function controlCheck(key, audit, detail) {
  return { key, ok: audit.ok === true, detail };
}

function main() {
  const writeReport = process.argv.includes('--write-report');
  const mainnetGate = runNodeScript('scripts/live-mainnet-listing-gate.js');
  const operatorCustody = runNodeScript('scripts/operator-key-custody-audit.js');
  const validatorEnvelopes = runNodeScript('scripts/audit-encrypted-validator-key-envelopes.js');
  const validatorDeliveryLock = runNodeScript('scripts/validator-key-delivery-locked-self-test.js');
  const dependencyCompatibility = runNodeScript('scripts/multichain-dependency-compatibility-self-test.js');
  const sourceRelease = publicSourceIntegrity();
  const dependencies = dependencyAudit();

  const checks = [
    controlCheck(
      'live_mainnet_operational_gate',
      { ok: mainnetGate.ok && mainnetGate.result?.technicalPreconditionsMet === true && mainnetGate.result?.passed === mainnetGate.result?.total },
      mainnetGate.result ? `${mainnetGate.result.passed}/${mainnetGate.result.total}; ${mainnetGate.result.status}.` : (mainnetGate.error || 'Unavailable')
    ),
    controlCheck(
      'operator_key_custody',
      operatorCustody,
      operatorCustody.ok ? 'Encrypted custody, ACL, identity binding, and signing locks passed.' : (operatorCustody.result?.message || operatorCustody.error || 'Failed')
    ),
    controlCheck(
      'validator_encrypted_envelopes',
      validatorEnvelopes,
      validatorEnvelopes.ok ? 'Four encrypted validator envelopes, target bindings, integrity, and ACLs passed.' : (validatorEnvelopes.result?.error || validatorEnvelopes.error || 'Failed')
    ),
    controlCheck(
      'validator_delivery_lock',
      validatorDeliveryLock,
      validatorDeliveryLock.ok ? 'Delivery, decryption, import, start, and arbitrary-command paths remain locked.' : (validatorDeliveryLock.result?.error || validatorDeliveryLock.error || 'Failed')
    ),
    controlCheck(
      'public_source_integrity',
      sourceRelease,
      sourceRelease.ok ? `Release ${sourceRelease.version}; SHA256 ${sourceRelease.sha256}.` : (sourceRelease.error || 'Manifest policy or package hash failed.')
    ),
    controlCheck(
      'production_dependency_audit',
      dependencies,
      dependencies.auditAvailable
        ? `critical=${dependencies.counts.critical || 0}, high=${dependencies.counts.high || 0}, moderate=${dependencies.counts.moderate || 0}, low=${dependencies.counts.low || 0}.`
        : (dependencies.error || 'Unavailable')
    ),
    controlCheck(
      'multichain_dependency_compatibility',
      dependencyCompatibility,
      dependencyCompatibility.ok
        ? 'TRON address/amount operations passed and the transfer path failed closed before signing.'
        : (dependencyCompatibility.result?.error || dependencyCompatibility.error || 'Failed')
    )
  ];
  const failed = checks.filter((row) => !row.ok);
  const report = {
    ok: failed.length === 0,
    schemaVersion: 'plais-internal-security-preaudit/v1',
    status: failed.length === 0 ? 'READY_FOR_EXTERNAL_AUDIT' : 'INTERNAL_REMEDIATION_REQUIRED',
    independent: false,
    independentExternalAuditComplete: false,
    auditor: 'PLAIS internal engineering pre-audit',
    conclusion: 'This report is not an independent security audit, certification, assurance opinion, or exchange approval.',
    productionWritesPerformed: false,
    passed: checks.length - failed.length,
    total: checks.length,
    blockers: failed.map((row) => ({ key: row.key, detail: row.detail })),
    checks,
    evidence: {
      mainnetGate: mainnetGate.result,
      operatorCustody: operatorCustody.result,
      validatorEnvelopes: validatorEnvelopes.result,
      validatorDeliveryLock: validatorDeliveryLock.result,
      publicSourceRelease: sourceRelease,
      productionDependencies: dependencies,
      multichainDependencyCompatibility: dependencyCompatibility.result
    },
    checkedAt: new Date().toISOString()
  };
  if (writeReport) {
    fs.mkdirSync(path.dirname(reportPath), { recursive: true });
    fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
  }
  console.log(JSON.stringify(report, null, 2));
  if (!report.ok) process.exitCode = 1;
}

main();
