// E2E test: Two clients connect to relay node and exchange messages
const net = require('net');
const http = require('http');

const SERVER = 'http://localhost:3001';
const RELAY_HOST = '127.0.0.1';
const RELAY_PORT = 4101;

function api(path, method = 'GET', body, token) {
  return new Promise((resolve, reject) => {
    const url = new URL(path, SERVER);
    const data = body ? JSON.stringify(body) : undefined;
    const opts = {
      hostname: url.hostname, port: url.port, path: url.pathname, method,
      headers: {
        'Content-Type': 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
      },
    };
    const req = http.request(opts, (res) => {
      let b = '';
      res.on('data', c => b += c);
      res.on('end', () => { try { resolve(JSON.parse(b)) } catch { resolve({error: b}) } });
    });
    req.on('error', reject);
    if (data) req.write(data);
    req.end();
  });
}

function frameParser() {
  let buf = Buffer.alloc(0);
  return {
    push(chunk) {
      buf = Buffer.concat([buf, chunk]);
      const msgs = [];
      while (buf.length >= 4) {
        const len = buf.readUInt32BE(0);
        if (buf.length < 4 + len) break;
        try {
          msgs.push(JSON.parse(buf.subarray(4, 4 + len).toString()));
        } catch {}
        buf = buf.subarray(4 + len);
      }
      return msgs;
    }
  };
}

function encodeMsg(msg) {
  const json = JSON.stringify(msg);
  const payload = Buffer.from(json, 'utf-8');
  const header = Buffer.alloc(4);
  header.writeUInt32BE(payload.length, 0);
  return Buffer.concat([header, payload]);
}

async function main() {
  console.log('=== E2E Relay Test ===\n');

  // 1. Register & login
  console.log('1. Register user...');
  const reg = await api('/api/auth/register', 'POST', { email: 'e2e@test.com', password: '123456' });
  const token = reg.data.token;
  console.log('   OK, user_id:', reg.data.user.id);

  // 2. Register two devices
  console.log('2. Register devices...');
  const d1 = await api('/api/devices/register', 'POST', {
    device_name: 'Client-A', device_fingerprint: 'e2e-fp-a', public_key: ''
  }, token);
  const d2 = await api('/api/devices/register', 'POST', {
    device_name: 'Client-B', device_fingerprint: 'e2e-fp-b', public_key: ''
  }, token);
  const devA = d1.data.id, devB = d2.data.id;
  console.log('   OK, Device-A:', devA, 'Device-B:', devB);

  // 3. Get relay node (assume already running)
  console.log('3. Get relay node...');
  const nodes = await api('/api/nodes/available', 'GET', null, token);
  if (!nodes.data || nodes.data.length === 0) {
    console.error('   FAIL: No relay nodes available. Make sure relay node is running.');
    process.exit(1);
  }
  const node = nodes.data[0];
  console.log('   OK, Node:', node.name, node.host + ':' + node.port);

  // 4. Request connection token for Device-A
  console.log('4. Request connection token...');
  const conn = await api('/api/connections/request', 'POST', {
    device_id: devA, node_id: node.id
  }, token);
  const connToken = conn.data.token;
  console.log('   OK, Token obtained');

  // 5. Connect Device-A to relay
  console.log('5. Connect Device-A to relay...');
  const socketA = new net.Socket();
  const parserA = frameParser();

  await new Promise((resolve, reject) => {
    socketA.connect(node.port, node.host, () => {
      // Send auth
      socketA.write(encodeMsg({
        type: 'auth', from_device_id: devA, to_device_id: 0,
        payload: JSON.stringify({ action: 'connect', device_id: devA, token: connToken }),
        timestamp: Date.now(), message_id: 'auth-1',
      }));
    });
    socketA.on('data', (chunk) => {
      for (const msg of parserA.push(chunk)) {
        if (msg.type === 'control') {
          const ctrl = JSON.parse(msg.payload);
          if (ctrl.action === 'connect_ok') {
            console.log('   OK, Device-A authenticated');
            resolve(null);
          }
        }
      }
    });
    socketA.on('error', reject);
    setTimeout(() => reject(new Error('Auth timeout')), 5000);
  });

  // 6. Connect Device-B to relay (simulate another client using same token pattern)
  console.log('6. Request token for Device-B and connect...');
  const conn2 = await api('/api/connections/request', 'POST', {
    device_id: devB, node_id: node.id
  }, token);
  const connToken2 = conn2.data.token;

  const socketB = new net.Socket();
  const parserB = frameParser();

  await new Promise((resolve, reject) => {
    socketB.connect(node.port, node.host, () => {
      socketB.write(encodeMsg({
        type: 'auth', from_device_id: devB, to_device_id: 0,
        payload: JSON.stringify({ action: 'connect', device_id: devB, token: connToken2 }),
        timestamp: Date.now(), message_id: 'auth-2',
      }));
    });
    socketB.on('data', (chunk) => {
      for (const msg of parserB.push(chunk)) {
        if (msg.type === 'control') {
          if (JSON.parse(msg.payload).action === 'connect_ok') {
            console.log('   OK, Device-B authenticated');
            resolve(null);
          }
        }
      }
    });
    socketB.on('error', reject);
    setTimeout(() => reject(new Error('Auth timeout')), 5000);
  });

  // 7. Device-A sends message to Device-B
  console.log('7. Device-A -> Device-B: "Hello from A!"');
  let msgReceived = false;
  socketB.on('data', (chunk) => {
    for (const msg of parserB.push(chunk)) {
      if (msg.type === 'data' && msg.payload === 'Hello from A!') {
        console.log('   OK, Device-B received: "' + msg.payload + '"');
        msgReceived = true;
      }
    }
  });

  socketA.write(encodeMsg({
    type: 'data', from_device_id: devA, to_device_id: devB,
    payload: 'Hello from A!',
    timestamp: Date.now(), message_id: 'msg-1',
  }));

  // Wait for message delivery
  await new Promise(r => setTimeout(r, 1000));

  // 8. Device-B sends reply
  console.log('8. Device-B -> Device-A: "Hello from B!"');
  let replyReceived = false;
  socketA.on('data', (chunk) => {
    for (const msg of parserA.push(chunk)) {
      if (msg.type === 'data' && msg.payload === 'Hello from B!') {
        console.log('   OK, Device-A received: "' + msg.payload + '"');
        replyReceived = true;
      }
    }
  });

  socketB.write(encodeMsg({
    type: 'data', from_device_id: devB, to_device_id: devA,
    payload: 'Hello from B!',
    timestamp: Date.now(), message_id: 'msg-2',
  }));

  await new Promise(r => setTimeout(r, 1000));

  // Cleanup
  socketA.destroy();
  socketB.destroy();

  console.log('\n=== Results ===');
  console.log('Message A->B:', msgReceived ? '✅ PASS' : '❌ FAIL');
  console.log('Message B->A:', replyReceived ? '✅ PASS' : '❌ FAIL');
  console.log('\n=== E2E Test Complete ===');
  process.exit(msgReceived && replyReceived ? 0 : 1);
}

main().catch(err => {
  console.error('Test failed:', err);
  process.exit(1);
});
