import { describe, it, expect } from 'vitest';
import { MessageSocket } from './message-socket';
import type { SocketLike } from './types';

class FakeSocket implements SocketLike {
  handlers = new Map<string, Array<(...a: unknown[]) => void>>();
  acks: Array<{ event: string; payload: unknown }> = [];
  ackReturn: unknown = { threadId: 't1', status: 'OPEN', history: [] };

  on(event: string, handler: (...a: unknown[]) => void): unknown {
    const list = this.handlers.get(event) ?? [];
    list.push(handler);
    this.handlers.set(event, list);
    return this;
  }
  off(event: string, handler: (...a: unknown[]) => void): unknown {
    this.handlers.set(event, (this.handlers.get(event) ?? []).filter((h) => h !== handler));
    return this;
  }
  emit(_event: string, ..._args: unknown[]): unknown {
    return this;
  }
  async emitWithAck(event: string, ...args: unknown[]): Promise<unknown> {
    this.acks.push({ event, payload: args[0] });
    return this.ackReturn;
  }
  connect(): unknown {
    return this;
  }
  disconnect(): unknown {
    return this;
  }
  trigger(event: string, ...args: unknown[]): void {
    (this.handlers.get(event) ?? []).forEach((h) => h(...args));
  }
  listenerCount(event: string): number {
    return (this.handlers.get(event) ?? []).length;
  }
}

const cfg = { serviceUrl: 'http://localhost:3200', token: 'tok' };

describe('MessageSocket facade', () => {
  it('on() registers a handler and the returned fn unsubscribes', () => {
    const fake = new FakeSocket();
    const ms = new MessageSocket(cfg, fake);
    const off = ms.on('message', () => {});
    expect(fake.listenerCount('message')).toBe(1);
    off();
    expect(fake.listenerCount('message')).toBe(0);
  });

  it('sendMessage invokes emitWithAck("send_message", payload)', async () => {
    const fake = new FakeSocket();
    fake.ackReturn = { id: 'm1', threadId: 't1', content: 'hello' };
    const ms = new MessageSocket(cfg, fake);
    await ms.sendMessage('t1', 'hello', { contentRef: 'sas://x' });
    const call = fake.acks.find((a) => a.event === 'send_message');
    expect(call?.payload).toMatchObject({ threadId: 't1', content: 'hello', contentRef: 'sas://x' });
  });

  it('openThread returns the ack and tracks the thread', async () => {
    const fake = new FakeSocket();
    fake.ackReturn = { threadId: 't9', status: 'OPEN', history: [] };
    const ms = new MessageSocket(cfg, fake);
    const r = await ms.openThread();
    expect(r.threadId).toBe('t9');
  });
});
