'use strict';

const assert = require('node:assert/strict');
const { once } = require('node:events');
const http = require('node:http');
const net = require('node:net');
const { spawn } = require('node:child_process');
const { test } = require('node:test');
const path = require('node:path');

class LineReader {
  constructor(stream) {
    this.lines = [];
    this.waiters = [];
    this.buffer = '';
    stream.setEncoding('utf8');
    stream.on('data', chunk => {
      this.buffer += chunk;
      const parts = this.buffer.split('\n');
      this.buffer = parts.pop();
      for (const line of parts) {
        this.push(line.replace(/\r$/, ''));
      }
    });
  }

  push(line) {
    const waiter = this.waiters.shift();
    if (waiter) {
      waiter.resolve(line);
    } else {
      this.lines.push(line);
    }
  }

  next(timeoutMs = 5000) {
    if (this.lines.length > 0) return Promise.resolve(this.lines.shift());
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        const index = this.waiters.indexOf(waiter);
        if (index >= 0) this.waiters.splice(index, 1);
        reject(new Error('Timed out waiting for a line'));
      }, timeoutMs);
      const waiter = {
        resolve: line => {
          clearTimeout(timeout);
          resolve(line);
        }
      };
      this.waiters.push(waiter);
    });
  }
}

function getFreePort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      const { port } = server.address();
      server.close(() => resolve(port));
    });
  });
}

function close(server) {
  if (!server) return Promise.resolve();
  return new Promise(resolve => server.close(() => resolve()));
}

test('reconnects after Burp starts and handles a later tool call', async () => {
  const port = await getFreePort();
  const bridgePath = path.join(__dirname, '..', 'mcp-bridge.js');
  const bridge = spawn(process.execPath, [bridgePath], {
    env: {
      ...process.env,
      BURP_MCP_HOST: '127.0.0.1',
      BURP_MCP_PORT: String(port),
      BURP_MCP_TOKEN: 'test-token'
    },
    stdio: ['pipe', 'pipe', 'pipe']
  });
  const stdout = new LineReader(bridge.stdout);
  const stderr = new LineReader(bridge.stderr);
  let server;
  const requests = [];

  try {
    const warning = await stderr.next();
    assert.match(warning, /Cannot connect to Burp/);

    server = http.createServer((request, response) => {
      if (request.method === 'GET' && request.url === '/tools') {
        response.writeHead(200, { 'Content-Type': 'application/json' });
        response.end(JSON.stringify(['proxy_history']));
        return;
      }

      let body = '';
      request.on('data', chunk => { body += chunk; });
      request.on('end', () => {
        requests.push(JSON.parse(body));
        response.writeHead(200, { 'Content-Type': 'application/json' });
        response.end(JSON.stringify({ ok: true }));
      });
    });
    await new Promise((resolve, reject) => {
      server.once('error', reject);
      server.listen(port, '127.0.0.1', resolve);
    });

    bridge.stdin.write(JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/list',
      params: {}
    }) + '\n');
    const listResponse = JSON.parse(await stdout.next());
    assert.equal(listResponse.id, 1);
    assert.equal(listResponse.result.tools[0].name, 'burp_proxy_history');

    bridge.stdin.write(JSON.stringify({
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/call',
      params: { name: 'burp_proxy_history', arguments: { limit: 1 } }
    }) + '\n');
    const callResponse = JSON.parse(await stdout.next());
    assert.equal(callResponse.id, 2);
    assert.deepEqual(requests[0], { tool: 'proxy_history', params: { limit: 1 } });
    assert.match(callResponse.result.content[0].text, /"ok": true/);
  } finally {
    const childClosed = bridge.exitCode === null && bridge.signalCode === null
      ? once(bridge, 'close')
      : Promise.resolve();
    bridge.stdin.end();
    bridge.kill();
    await close(server);
    await childClosed.catch(() => {});
  }
});
