require('../config/loadEnv')();
const fs = require('fs');
const crypto = require('crypto');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const canonicalLedgerService = require('../services/canonicalLedgerService');

const projectRoot = path.join(__dirname, '..');
const bridgeRoot = path.join(projectRoot, 'runtime', 'plais-qbft-public-bridge');
const configPath = path.join(bridgeRoot, 'config.json');
const publicKeyPath = path.join(bridgeRoot, 'data', 'key.pub');
const attestationPath = path.join(bridgeRoot, 'sync-attestation.json');
const pm2Name = 'plais-qbft-public-bridge';
const syncCompletionLog = 'Node is in sync, enabling transaction handling';
const maxLogBytes = 64 * 1024 * 1024;
const maxHeadAgeMs = 5 * 60 * 1000;
const maxGapBlocks = 10;
// During the validator handoff, the bridge remains connected to the two
// central validators that have not yet moved.  Requiring all four legacy
// local validator peers would incorrectly invalidate a healthy bridge after
// the first safe handoffs.  Two independent live peers are sufficient to
// attest that this non-validator, RPC-disabled bridge follows the head.
const minPeerCount = 2;

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

function stripAnsi(value) {
  return String(value || '').replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, '');
}

function parseBesuDate(value) {
  const normalized = String(value || '')
    .replace(' ', 'T')
    .replace(/([+-]\d{2})(\d{2})$/, '$1:$2');
  const parsed = new Date(normalized);
  return Number.isFinite(parsed.getTime()) ? parsed : null;
}

function parseBridgeLog(rawLog) {
  const log = stripAnsi(rawLog);
  const syncPattern = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{4}).*Node is in sync, enabling transaction handling/g;
  const headPattern = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{4}).*Imported (?:empty )?block #([\d,]+).*Peers:\s*(\d+)/g;
  let syncMatch = null;
  let headMatch = null;
  for (const match of log.matchAll(syncPattern)) syncMatch = match;
  for (const match of log.matchAll(headPattern)) headMatch = match;
  return {
    syncCompletionAt: syncMatch ? parseBesuDate(syncMatch[1]) : null,
    latestBlockObservedAt: headMatch ? parseBesuDate(headMatch[1]) : null,
    bridgeBlock: headMatch ? Number(headMatch[2].replace(/,/g, '')) : null,
    peerCount: headMatch ? Number(headMatch[3]) : null
  };
}

function readTail(filePath, byteLimit = maxLogBytes) {
  const stat = fs.statSync(filePath);
  const length = Math.min(stat.size, byteLimit);
  const buffer = Buffer.alloc(length);
  const descriptor = fs.openSync(filePath, 'r');
  try {
    fs.readSync(descriptor, buffer, 0, length, stat.size - length);
  } finally {
    fs.closeSync(descriptor);
  }
  return buffer.toString('utf8');
}

function getBridgePid() {
  const executable = process.platform === 'win32' ? 'cmd.exe' : 'pm2';
  const args = process.platform === 'win32'
    ? ['/d', '/c', path.join(process.env.APPDATA || '', 'npm', 'pm2.cmd'), 'pid', pm2Name]
    : ['pid', pm2Name];
  const result = spawnSync(executable, args, {
    encoding: 'utf8',
    windowsHide: true,
    timeout: 5000
  });
  if (result.error || result.status !== 0) return 0;
  const pid = Number(String(result.stdout || '').trim().split(/\s+/).pop());
  return Number.isInteger(pid) && pid > 0 ? pid : 0;
}

function resolveOutLogPath() {
  if (process.env.PLAIS_QBFT_PUBLIC_BRIDGE_OUT_LOG) {
    return path.resolve(process.env.PLAIS_QBFT_PUBLIC_BRIDGE_OUT_LOG);
  }
  return path.join(os.homedir(), '.pm2', 'logs', `${pm2Name}-out.log`);
}

function writeJsonAtomic(filePath, value) {
  const temporaryPath = `${filePath}.${process.pid}.tmp`;
  fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
  try {
    fs.renameSync(temporaryPath, filePath);
  } catch (error) {
    try { fs.unlinkSync(temporaryPath); } catch (_) {}
    throw error;
  }
}

function selfTest() {
  const fixture = [
    '2026-07-30 15:22:32.597+0900 | INFO | Node is in sync, enabling transaction handling',
    '\u001b[32m2026-07-30 15:31:14.123+0900\u001b[0m | INFO | Imported empty block #520,497 / 0 tx. Peers: 4'
  ].join('\n');
  const parsed = parseBridgeLog(fixture);
  const ok = parsed.syncCompletionAt?.toISOString() === '2026-07-30T06:22:32.597Z'
    && parsed.latestBlockObservedAt?.toISOString() === '2026-07-30T06:31:14.123Z'
    && parsed.bridgeBlock === 520497
    && parsed.peerCount === 4;
  if (!ok) throw new Error('BRIDGE_LOG_PARSER_SELF_TEST_FAILED');
  console.log(JSON.stringify({ ok: true, check: 'bridge-sync-attestation-parser' }, null, 2));
}

async function main() {
  if (process.argv.includes('--self-test')) {
    selfTest();
    return;
  }
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  if (config.schemaVersion !== 'plais-qbft-public-bridge/v1' || config.enabled !== true) {
    throw new Error('PUBLIC_BRIDGE_CONFIG_NOT_ENABLED');
  }
  const pid = getBridgePid();
  if (!pid) throw new Error('PUBLIC_BRIDGE_PM2_PROCESS_NOT_ONLINE');

  const parsed = parseBridgeLog(readTail(resolveOutLogPath()));
  if (!parsed.syncCompletionAt) throw new Error('PUBLIC_BRIDGE_SYNC_COMPLETION_LOG_MISSING');
  if (!parsed.latestBlockObservedAt || !Number.isSafeInteger(parsed.bridgeBlock)) {
    throw new Error('PUBLIC_BRIDGE_LIVE_HEAD_LOG_MISSING');
  }
  const headAgeMs = Date.now() - parsed.latestBlockObservedAt.getTime();
  if (headAgeMs < -60 * 1000 || headAgeMs > maxHeadAgeMs) {
    throw new Error(`PUBLIC_BRIDGE_HEAD_LOG_STALE:${Math.round(headAgeMs / 1000)}s`);
  }
  if (!Number.isInteger(parsed.peerCount) || parsed.peerCount < minPeerCount) {
    throw new Error(`PUBLIC_BRIDGE_PEER_COUNT_INSUFFICIENT:${parsed.peerCount}`);
  }

  const canonical = await canonicalLedgerService.refreshStatus();
  if (canonical.online !== true || Number(canonical.chainId) !== 20260718) {
    throw new Error(`CANONICAL_RPC_NOT_READY:${canonical.error || 'UNKNOWN'}`);
  }
  const canonicalBlock = Number(canonical.blockNumber);
  const gap = canonicalBlock - parsed.bridgeBlock;
  if (!Number.isSafeInteger(canonicalBlock) || gap < 0 || gap > maxGapBlocks) {
    throw new Error(`PUBLIC_BRIDGE_HEAD_GAP_INVALID:${gap}`);
  }

  const observedAt = new Date().toISOString();
  const attestation = {
    schemaVersion: 'plais-qbft-public-bridge-sync-attestation/v1',
    status: 'SYNCED_AND_FOLLOWING_HEAD',
    chainId: 20260718,
    bridge: {
      host: config.advertisedHost,
      port: Number(config.p2pPort),
      pm2Name,
      pid,
      validatorEnabled: false,
      rpcEnabled: false
    },
    bindings: {
      configSha256: sha256File(configPath),
      publicKeySha256: sha256File(publicKeyPath)
    },
    evidence: {
      syncCompletionLog,
      syncCompletionAt: parsed.syncCompletionAt.toISOString(),
      latestBlockObservedAt: parsed.latestBlockObservedAt.toISOString(),
      bridgeBlock: parsed.bridgeBlock,
      canonicalBlock,
      observedGapBlocks: gap,
      peerCount: parsed.peerCount,
      liveHeadFollowing: true
    },
    observedAt
  };
  writeJsonAtomic(attestationPath, attestation);
  console.log(JSON.stringify({
    ok: true,
    attestationPath,
    bridgePid: pid,
    bridgeBlock: parsed.bridgeBlock,
    canonicalBlock,
    gap,
    peerCount: parsed.peerCount,
    latestBlockObservedAt: parsed.latestBlockObservedAt.toISOString(),
    observedAt
  }, null, 2));
}

if (require.main === module) {
  main().catch((error) => {
    console.error(JSON.stringify({ ok: false, error: error.message }, null, 2));
    process.exit(1);
  });
}

module.exports = { parseBridgeLog };
