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

const ROOT = path.resolve(__dirname, '..');
const NODE = process.execPath;
const SERVER = path.join(ROOT, 'src', 'server.js');
const MULTI_DIR = path.join(ROOT, 'data', 'peer-block-sync-sample');
const PORT_A = 3200;
const PORT_B = 3201;

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function jsonFetch(url, options = {}) {
  const response = await fetch(url, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
  });
  const data = await response.json().catch(() => ({}));
  if (!response.ok || data.error) throw new Error(`${url} failed: ${data.error || data.message || response.status}`);
  return data;
}

async function waitForHealth(port) {
  for (let index = 0; index < 50; index += 1) {
    try {
      return await jsonFetch(`http://localhost:${port}/api/health`);
    } catch {
      await sleep(100);
    }
  }
  throw new Error(`Node ${port} did not become healthy`);
}

function startNode(port, name) {
  const dataDir = path.join(MULTI_DIR, name);
  fs.mkdirSync(dataDir, { recursive: true });
  return spawn(NODE, [SERVER], {
    cwd: ROOT,
    env: {
      ...process.env,
      PORT: String(port),
      MAZEX_DATA_DIR: dataDir,
      MAZEX_AUTO_BROADCAST: '0',
      MAZEX_SYNC_INTERVAL_MS: '0',
    },
    stdio: ['ignore', 'pipe', 'pipe'],
    windowsHide: true,
  });
}

async function main() {
  fs.rmSync(MULTI_DIR, { recursive: true, force: true });
  const children = [startNode(PORT_A, 'node-a'), startNode(PORT_B, 'node-b')];
  try {
    await Promise.all([waitForHealth(PORT_A), waitForHealth(PORT_B)]);
    await jsonFetch(`http://localhost:${PORT_A}/api/base1/reset`, { method: 'POST', body: '{}' });
    await jsonFetch(`http://localhost:${PORT_B}/api/base1/reset`, { method: 'POST', body: '{}' });

    const beforeA = await jsonFetch(`http://localhost:${PORT_A}/api/base1/chain/summary`);
    const beforeB = await jsonFetch(`http://localhost:${PORT_B}/api/base1/chain/summary`);
    if (beforeA.head_hash !== beforeB.head_hash) throw new Error('Nodes must share deterministic genesis');

    const solve = await jsonFetch(`http://localhost:${PORT_A}/api/base1/demo-solve`, {
      method: 'POST',
      body: JSON.stringify({ solver: 'peer-block-sync-solver' }),
    });
    if (!solve.correct) throw new Error(`demo solve failed: ${solve.message}`);

    const sync = await jsonFetch(`http://localhost:${PORT_B}/api/base1/node/sync-peer`, {
      method: 'POST',
      body: JSON.stringify({ url: `http://localhost:${PORT_A}` }),
    });
    if (!sync.ok || !sync.blocks?.adopted) {
      throw new Error(`Expected node-b to adopt node-a after block-first sync: ${sync.blocks?.status || sync.snapshot?.status}`);
    }

    const afterA = await jsonFetch(`http://localhost:${PORT_A}/api/base1/chain/summary`);
    const afterB = await jsonFetch(`http://localhost:${PORT_B}/api/base1/chain/summary`);
    if (afterA.head_hash !== afterB.head_hash) throw new Error('Expected node-b head to match node-a');

    console.table([
      {
        node: 'node-a',
        phase: 'after solve',
        height: afterA.height,
        work: afterA.cumulative_work_score,
        head: afterA.head_hash.slice(0, 16),
      },
      {
        node: 'node-b',
        phase: 'after sync-peer',
        height: afterB.height,
        work: afterB.cumulative_work_score,
        head: afterB.head_hash.slice(0, 16),
      },
      {
        node: 'sync',
        phase: sync.blocks.status,
        height: sync.blocks.accepted_count,
        work: sync.blocks.candidate_chain.cumulative_work_score,
        head: sync.blocks.candidate_chain.head_hash.slice(0, 16),
      },
    ]);
  } finally {
    for (const child of children) child.kill();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
