// P2 realtime smoke: two socket.io clients (Alice, Bob) on one thread.
// Requires the service running with IIOS_DEV_TOKENS=1 + APP_SECRETS set.
// Run: node scripts/smoke-realtime.mjs
import 'dotenv/config';
import { io } from 'socket.io-client';
import jwt from 'jsonwebtoken';
import { PrismaClient } from '@prisma/client';

const SERVICE = process.env.SMOKE_URL ?? 'http://localhost:3200';
const APP_ID = 'portal-demo';
const SECRET = JSON.parse(process.env.APP_SECRETS ?? '{"portal-demo":"dev-secret"}')[APP_ID];
const prisma = new PrismaClient();

const sign = (userId) =>
  jwt.sign({ sub: userId, name: userId, appId: APP_ID, orgId: `org_${APP_ID}` }, SECRET, {
    algorithm: 'HS256',
    expiresIn: '1h',
  });

const connect = (userId) =>
  io(`${SERVICE}/message`, { auth: { token: sign(userId) }, transports: ['websocket'], forceNew: true });

const once = (socket, event) => new Promise((res) => socket.once(event, res));
const assert = (cond, msg) => {
  if (!cond) {
    console.error('✗', msg);
    process.exit(1);
  }
  console.log('✓', msg);
};

async function unread(threadId, userId) {
  const handle = await prisma.iiosSourceHandle.findFirst({ where: { externalId: userId } });
  if (!handle) return 0;
  const actor = await prisma.iiosActorRef.findFirst({ where: { sourceHandleId: handle.id } });
  if (!actor) return 0;
  const c = await prisma.iiosUnreadCounter.findUnique({
    where: { threadId_actorId: { threadId, actorId: actor.id } },
  });
  return c?.unreadCount ?? 0;
}

const alice = connect('alice');
const bob = connect('bob');

try {
  // Alice creates a thread; membership is governed, so Alice ADDS Bob (he can't self-join).
  const opened = await alice.emitWithAck('open_thread', {});
  const threadId = opened.threadId;
  assert(!!threadId, `Alice created thread ${threadId}`);
  await alice.emitWithAck('add_participant', { threadId, userId: 'bob' });
  await bob.emitWithAck('open_thread', { threadId });

  // Alice sends; Bob should receive it live.
  const bobGetsMessage = once(bob, 'message');
  const sent = await alice.emitWithAck('send_message', { threadId, content: 'hi bob' });
  const received = await bobGetsMessage;
  assert(received.content === 'hi bob', `Bob received "${received.content}" in realtime`);
  assert((await unread(threadId, 'bob')) === 1, "Bob's unread = 1");

  // Bob reads; Alice should get the receipt; Bob's unread resets.
  const aliceGetsReceipt = once(alice, 'receipt');
  await bob.emitWithAck('read', { threadId, interactionId: sent.id });
  const receipt = await aliceGetsReceipt;
  assert(receipt.kind === 'READ' && receipt.interactionId === sent.id, 'Alice received READ receipt');
  assert((await unread(threadId, 'bob')) === 0, "Bob's unread reset to 0");

  console.log('\nP2 realtime smoke: PASS');
} finally {
  alice.close();
  bob.close();
  await prisma.$disconnect();
}
process.exit(0);
