#!/usr/bin/env node
const WebSocket = require('ws');
const https = require('https');

const ROOM = process.argv[2] || 'SCWX';
const HOST = 'ecast-prod-use2.jackboxgames.com';

function ts() { return new Date().toISOString().slice(11, 23); }
function log(tag, ...args) { console.log(`[${ts()}][${tag}]`, ...args); }

function connect(name, opts = {}) {
  return new Promise((resolve, reject) => {
    let params = `role=player&name=${encodeURIComponent(name)}&format=json`;
    if (opts.secret) {
      params += `&secret=${opts.secret}`;
      params += `&id=${opts.id}`;
    } else {
      params += `&userId=${name}-${Date.now()}`;
    }
    const url = `wss://${HOST}/api/v2/rooms/${ROOM}/play?${params}`;
    log(name, 'Connecting:', url);

    const ws = new WebSocket(url, ['ecast-v0'], {
      headers: { 'Origin': 'https://jackbox.tv' }
    });

    const allMsgs = [];
    ws.on('message', (raw) => {
      const m = JSON.parse(raw.toString());
      allMsgs.push(m);

      if (m.opcode === 'client/welcome') {
        const r = m.result;
        const hereList = r.here ? Object.entries(r.here).map(([k, v]) => {
          const role = Object.keys(v.roles)[0];
          const detail = v.roles.player ? `(${v.roles.player.name})` : '';
          return `${k}:${role}${detail}`;
        }).join(', ') : 'null';
        log(name, `WELCOME id=${r.id} reconnect=${r.reconnect} secret=${r.secret} here=[${hereList}]`);
        resolve({ ws, id: r.id, secret: r.secret, msgs: allMsgs, name });
      } else if (m.opcode === 'client/connected') {
        const r = m.result;
        log(name, `*** CLIENT/CONNECTED id=${r.id} userId=${r.userId} name=${r.name} role=${JSON.stringify(r.roles || r.role)}`);
      } else if (m.opcode === 'client/disconnected') {
        const r = m.result;
        log(name, `*** CLIENT/DISCONNECTED id=${r.id} role=${JSON.stringify(r.roles || r.role)}`);
      } else if (m.opcode === 'client/kicked') {
        log(name, `*** CLIENT/KICKED:`, JSON.stringify(m.result));
      } else if (m.opcode === 'error') {
        log(name, `ERROR code=${m.result.code}: ${m.result.msg}`);
        reject(new Error(m.result.msg));
      } else if (m.opcode === 'object') {
        const r = m.result;
        if (r.key === 'room') {
          log(name, `ROOM state=${r.val?.state} lobbyState=${r.val?.lobbyState} gameCanStart=${r.val?.gameCanStart} gameFinished=${r.val?.gameFinished} v${r.version}`);
        } else if (r.key === 'textDescriptions') {
          const latest = r.val?.latestDescriptions?.[0];
          if (latest) log(name, `TEXT: "${latest.text}" (${latest.category})`);
        } else if (r.key?.startsWith('player:')) {
          log(name, `PLAYER ${r.key} state=${r.val?.state || 'init'} v${r.version}`);
        } else {
          log(name, `ENTITY ${r.key} v${r.version}`);
        }
      } else if (m.opcode === 'ok') {
        log(name, `OK seq response`);
      } else if (m.opcode === 'room/get-audience') {
        log(name, `AUDIENCE connections=${m.result?.connections}`);
      } else {
        log(name, `OTHER op=${m.opcode}`, JSON.stringify(m.result).slice(0, 200));
      }
    });

    ws.on('close', (code, reason) => {
      log(name, `CLOSED code=${code} reason=${reason.toString()}`);
    });
    ws.on('error', (e) => {
      log(name, `WS_ERROR: ${e.message}`);
      reject(e);
    });
  });
}

function wait(ms) { return new Promise(r => setTimeout(r, ms)); }

async function main() {
  log('TEST', `=== Phase 1: Connect P1 to ${ROOM} ===`);
  const p1 = await connect('P1');
  log('TEST', `P1 connected as id=${p1.id}, secret=${p1.secret}`);

  log('TEST', `=== Phase 2: Check connections ===`);
  const connBefore = await fetchJSON(`https://ecast.jackboxgames.com/api/v2/rooms/${ROOM}/connections`);
  log('TEST', `Connections: ${JSON.stringify(connBefore)}`);

  await wait(2000);

  log('TEST', `=== Phase 3: Connect P2 (watch P1 for client/connected) ===`);
  const p2 = await connect('P2');
  log('TEST', `P2 connected as id=${p2.id}, secret=${p2.secret}`);

  const connAfterJoin = await fetchJSON(`https://ecast.jackboxgames.com/api/v2/rooms/${ROOM}/connections`);
  log('TEST', `Connections after P2 join: ${JSON.stringify(connAfterJoin)}`);

  await wait(2000);

  log('TEST', `=== Phase 4: P2 gracefully disconnects (watch P1 for client/disconnected) ===`);
  p2.ws.close(1000, 'test-disconnect');
  await wait(3000);

  const connAfterLeave = await fetchJSON(`https://ecast.jackboxgames.com/api/v2/rooms/${ROOM}/connections`);
  log('TEST', `Connections after P2 leave: ${JSON.stringify(connAfterLeave)}`);

  log('TEST', `=== Phase 5: Reconnect P2 using secret ===`);
  const p2r = await connect('P2-RECONNECT', { secret: p2.secret, id: p2.id });
  log('TEST', `P2 reconnected as id=${p2r.id}, reconnect should be true`);

  const connAfterReconnect = await fetchJSON(`https://ecast.jackboxgames.com/api/v2/rooms/${ROOM}/connections`);
  log('TEST', `Connections after P2 reconnect: ${JSON.stringify(connAfterReconnect)}`);

  await wait(2000);

  log('TEST', `=== Phase 6: Connect P3 (reach 3 players for CanStart) ===`);
  const p3 = await connect('P3');
  log('TEST', `P3 connected as id=${p3.id}`);

  await wait(2000);

  log('TEST', `=== Phase 7: Query audience count ===`);
  p1.ws.send(JSON.stringify({ seq: 100, opcode: 'room/get-audience', params: {} }));
  await wait(1000);

  log('TEST', `=== Phase 8: All messages received by P1 ===`);
  const p1Opcodes = p1.msgs.map(m => `pc:${m.pc} ${m.opcode}${m.result?.key ? ':' + m.result.key : ''}`);
  log('TEST', `P1 received ${p1.msgs.length} messages: ${p1Opcodes.join(', ')}`);

  log('TEST', `=== Cleanup ===`);
  p1.ws.close(1000);
  p2r.ws.close(1000);
  p3.ws.close(1000);
  await wait(1000);

  const connFinal = await fetchJSON(`https://ecast.jackboxgames.com/api/v2/rooms/${ROOM}/connections`);
  log('TEST', `Final connections: ${JSON.stringify(connFinal)}`);
  log('TEST', `=== DONE ===`);
  process.exit(0);
}

function fetchJSON(url) {
  return new Promise((resolve, reject) => {
    https.get(url, res => {
      let d = '';
      res.on('data', c => d += c);
      res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(e); } });
    }).on('error', reject);
  });
}

main().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
