// Runs the shared adapter conformance suite against KernelClientAdapter — proving it satisfies
// the same contract as the mock, over the REAL MessageSocket facade. Only the lowest socket.io
// layer is faked (via kernel-client's own SocketLike seam), so the facade's wire mapping is
// exercised for real. No live IIOS required.

import { describe, it, expect } from 'vitest';
import { MessageSocket } from '@insignia/iios-kernel-client';
import type { Message as KernelMessage, SocketLike } from '@insignia/iios-kernel-client';
import { runAdapterConformance } from '../conformance';
import { KernelClientAdapter, type RestPort } from './kernel-client';

const ME = 'me';
const SEEDED = 'th_seed';

/** An in-memory stand-in for the /message socket.io namespace: stores messages, echoes 'message'. */
function makeFakeSocket(): SocketLike {
  const threads = new Map<string, KernelMessage[]>([
    [
      SEEDED,
      [
        {
          id: 'seed_1',
          threadId: SEEDED,
          senderActorId: 'actor_other',
          senderId: 'pp_other',
          senderName: 'Other',
          content: 'seeded message',
          createdAt: new Date(0).toISOString(),
        },
      ],
    ],
  ]);
  const handlers = new Map<string, Set<(...a: unknown[]) => void>>();
  let seq = 1;

  const fire = (event: string, payload: unknown): void => handlers.get(event)?.forEach((h) => h(payload));

  return {
    on(event, handler) {
      if (!handlers.has(event)) handlers.set(event, new Set());
      handlers.get(event)!.add(handler);
      return undefined;
    },
    off(event, handler) {
      handlers.get(event)?.delete(handler);
      return undefined;
    },
    emit() {
      return undefined; // typing/focus — no echo needed for conformance
    },
    async emitWithAck(event, payload) {
      const p = (payload ?? {}) as {
        threadId: string;
        content?: string;
        parentInteractionId?: string;
        interactionId?: string;
        type?: string;
        value?: string;
      };
      if (event === 'open_thread') {
        if (!threads.has(p.threadId)) threads.set(p.threadId, []);
        return { threadId: p.threadId, status: 'OPEN', history: [...threads.get(p.threadId)!] };
      }
      if (event === 'send_message') {
        const msg: KernelMessage = {
          id: `m_${seq++}`,
          threadId: p.threadId,
          senderActorId: `actor_${ME}`,
          senderId: ME,
          senderName: ME,
          content: p.content ?? '',
          createdAt: new Date().toISOString(),
          ...(p.parentInteractionId ? { parentInteractionId: p.parentInteractionId } : {}),
        };
        if (!threads.has(p.threadId)) threads.set(p.threadId, []);
        threads.get(p.threadId)!.push(msg);
        fire('message', msg);
        return msg;
      }
      if (event === 'read') return { ok: true };
      if (event === 'annotate') {
        fire('annotation', {
          threadId: p.threadId,
          interactionId: p.interactionId,
          type: p.type,
          value: p.value,
          op: 'add',
          users: [ME],
          userId: ME,
        });
        return {};
      }
      return {};
    },
    connect() {
      return undefined;
    },
    disconnect() {
      return undefined;
    },
    connected: true,
  };
}

function makeFakeRest(): RestPort {
  let seq = 1;
  return {
    async listThreads() {
      return [];
    },
    async createThread() {
      return { threadId: `th_new_${seq++}` };
    },
    async addParticipant() {
      /* governed server-side; a fake always allows */
    },
    async discoverThreads() {
      return [
        {
          threadId: 'th_pub',
          subject: 'general',
          metadata: { membership: 'channel', visibility: 'public', topic: 'Company-wide' },
          participantCount: 3,
          joined: false,
        },
      ];
    },
    async leaveThread(threadId) {
      return { threadId, participantCount: 0 };
    },
  };
}

function makeAdapter(): KernelClientAdapter {
  const socket = new MessageSocket({ serviceUrl: 'http://iios.test', token: 'tok', autoConnect: false }, makeFakeSocket());
  return new KernelClientAdapter({ currentUserId: ME, socket, rest: makeFakeRest() });
}

runAdapterConformance({ makeAdapter, seededThreadId: SEEDED, openWith: ['pp_a'] });

describe('KernelClientAdapter channels', () => {
  it('browse maps discovered public channels; create/join/leave delegate to the transport', async () => {
    const adapter = makeAdapter();
    const list = await adapter.browseChannels!();
    expect(list[0]).toMatchObject({ threadId: 'th_pub', name: 'general', visibility: 'public', joined: false, memberCount: 3, topic: 'Company-wide' });

    const { threadId } = await adapter.createChannel!({ name: 'design', topic: 'UI', visibility: 'public' });
    expect(typeof threadId).toBe('string');

    await expect(adapter.joinChannel!('th_pub')).resolves.toBeUndefined();
    await expect(adapter.leaveChannel!('th_pub')).resolves.toBeUndefined();
  });
});
