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', 'p2p-network-sample');
const PORT_A = 3800;
const PORT_B = 3801;
const P2P_A = 4800;
const P2P_B = 4801;

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 < 80; index += 1) {
    try {
      return await jsonFetch(`http://localhost:${port}/api/health`);
    } catch {
      await sleep(100);
    }
  }
  throw new Error(`Node ${port} did not become healthy`);
}

async function waitForSameHead() {
  for (let index = 0; index < 80; index += 1) {
    const [a, b] = await Promise.all([
      jsonFetch(`http://localhost:${PORT_A}/api/base1/chain/summary`),
      jsonFetch(`http://localhost:${PORT_B}/api/base1/chain/summary`),
    ]);
    if (a.head_hash === b.head_hash && a.height > 0) return { a, b };
    await sleep(250);
  }
  throw new Error('P2P nodes did not converge to the same head');
}

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

async function main() {
  fs.rmSync(MULTI_DIR, { recursive: true, force: true });
  const children = [
    startNode({ httpPort: PORT_A, p2pPort: P2P_A, name: 'node-a' }),
    startNode({ httpPort: PORT_B, p2pPort: P2P_B, name: '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 connect = await jsonFetch(`http://localhost:${PORT_A}/api/base1/p2p/connect`, {
      method: 'POST',
      body: JSON.stringify({ peer: `tcp://127.0.0.1:${P2P_B}` }),
    });
    if (!connect.ok) throw new Error(`P2P connect failed: ${connect.code}`);

    await sleep(750);
    const before = await Promise.all([
      jsonFetch(`http://localhost:${PORT_A}/api/base1/p2p/status`),
      jsonFetch(`http://localhost:${PORT_B}/api/base1/p2p/status`),
    ]);
    if (!before[0].enabled || !before[1].enabled || before[0].peer_count < 1 || before[1].peer_count < 1) {
      throw new Error('Expected both nodes to have an active P2P connection');
    }
    const security = await jsonFetch(`http://localhost:${PORT_A}/api/base1/security/status`);
    if (!security.api_max_body_bytes || !security.api_rate_limit) {
      throw new Error('Expected mainnet security status to expose active limits');
    }

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

    await jsonFetch(`http://localhost:${PORT_A}/api/base1/p2p/broadcast-head`, { method: 'POST', body: '{}' });
    const heads = await waitForSameHead();
    const after = await Promise.all([
      jsonFetch(`http://localhost:${PORT_A}/api/base1/p2p/status`),
      jsonFetch(`http://localhost:${PORT_B}/api/base1/p2p/status`),
    ]);

    console.table([
      {
        node: 'node-a',
        p2p_peers: after[0].peer_count,
        height: heads.a.height,
        work: heads.a.cumulative_work_score,
        head: heads.a.head_hash.slice(0, 16),
      },
      {
        node: 'node-b',
        p2p_peers: after[1].peer_count,
        height: heads.b.height,
        work: heads.b.cumulative_work_score,
        head: heads.b.head_hash.slice(0, 16),
      },
      {
        node: 'security',
        p2p_peers: '',
        height: security.api_rate_limit,
        work: security.api_post_rate_limit,
        head: `max body ${security.api_max_body_bytes}`,
      },
    ]);
  } finally {
    for (const child of children) child.kill();
  }
}

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