import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  FederationPeer, TabInstanceResolver, ExactInstanceResolver,
} from '@open-matrix/federation/federation_peer';
import { InMemoryExactBroker, InMemoryFilterBroker } from '@open-matrix/federation/transport/inmem';
import { getInternalTopic } from '@open-matrix/federation/topics';

/**
 * Unit tests for FederationPeer (formerly CrossRealmRouter).
 *
 * These tests verify:
 * - Local routing via instanceResolver
 * - Cross-realm detection (no backbone = error)
 * - TabInstanceResolver topic rewriting
 * - ExactInstanceResolver exact matching
 * - invoke() request/reply semantics
 */

test('FederationPeer: routes local messages to local transport (TabInstanceResolver)', async () => {
  const localBus = new InMemoryFilterBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    instanceResolver: new TabInstanceResolver('flowpad.tab-123'),
  });

  // Track published messages
  const published: Array<{ topic: string; payload: string }> = [];
  localBus.subscribe('flowpad.tab-123.component.accepts.msg', (topic, payload) => {
    published.push({ topic, payload });
  });

  // Route to local realm - TabInstanceResolver rewrites slash to dot format
  await router.route('flowpad.tab-123/component.accepts.msg', { test: true });

  assert.equal(published.length, 1);
  assert.equal(published[0].topic, 'flowpad.tab-123.component.accepts.msg');
  assert.deepEqual(JSON.parse(published[0].payload), { test: true });
});

test('FederationPeer: base realm routes locally with TabInstanceResolver', async () => {
  const localBus = new InMemoryFilterBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    instanceResolver: new TabInstanceResolver('flowpad.tab-123'),
  });

  const published: Array<{ topic: string; payload: string }> = [];
  localBus.subscribe('flowpad.tab-123.component.accepts.msg', (topic, payload) => {
    published.push({ topic, payload });
  });

  // Route using base realm "flowpad" - should be rewritten to tab realm
  await router.route('flowpad.component.accepts.msg', { test: true });

  assert.equal(published.length, 1);
  assert.ok(published[0].topic.includes('flowpad.tab-123'), 'Topic should include tab realm');
});

test('FederationPeer: ExactInstanceResolver rejects different realms', async () => {
  const localBus = new InMemoryExactBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    // Default: ExactInstanceResolver
  });

  // flowpad.tab-456 is a DIFFERENT realm for ExactInstanceResolver
  // Without backbone, cross-realm route just warns (no error)
  const published: Array<{ topic: string; payload: string }> = [];
  localBus.subscribe('flowpad.tab-456.component.accepts.msg', (topic, payload) => {
    published.push({ topic, payload });
  });

  await router.route('flowpad.tab-456/component.accepts.msg', { test: true });

  // Should NOT be delivered locally (different realm, no backbone)
  assert.equal(published.length, 0, 'Different tab suffixes should not route locally');
});

test('FederationPeer: cross-realm publishes to backbone ingress topic', async () => {
  const localBus = new InMemoryExactBroker();
  const backbone = new InMemoryFilterBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    backbone,
  });

  // Track messages on backbone
  const backboneMessages: Array<{ topic: string; payload: string }> = [];
  backbone.subscribe('daemon.$ingress.00', (topic, payload) => {
    backboneMessages.push({ topic, payload });
  });

  // Route to foreign realm
  await router.route('daemon/project.accepts.scan', { path: '/home' });

  assert.equal(backboneMessages.length, 1);
  // Should use ingress topic format: {realm}.$ingress.{shard}
  assert.equal(backboneMessages[0].topic, 'daemon.$ingress.00');

  // Payload should be wrapped with sourceRealm
  const envelope = JSON.parse(backboneMessages[0].payload);
  assert.equal(envelope.sourceRoot, 'flowpad.tab-123');
});

// ═══════════════════════════════════════════════════════════════════════════
// invoke() method tests
// ═══════════════════════════════════════════════════════════════════════════

test('FederationPeer.invoke: local invoke with request/reply semantics', async () => {
  const localBus = new InMemoryFilterBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    instanceResolver: new TabInstanceResolver('flowpad.tab-123'),
  });

  // Set up a responder on the local bus.
  // With DNS hierarchical addressing, getInternalTopic returns semantic paths.
  // FP-21-3: Cmd routes to $inbox with MxEnvelope format
  const browserTopic = 'component/$inbox';
  localBus.subscribe(browserTopic, (topic, payload) => {
    const request = JSON.parse(payload);
    const reply = {
      ok: true,
      result: { name: 'TestComponent', version: '1.0' },
    };
    localBus.publish(request.replyTo, JSON.stringify(reply));
  });

  // Invoke local method
  const result = await router.invoke<{ name: string; version: string }>(
    'flowpad.tab-123/component.accepts.getInfo',
    {},
    1000
  );

  assert.equal(result.name, 'TestComponent');
  assert.equal(result.version, '1.0');
});

test('FederationPeer.invoke: local invoke timeout', async () => {
  const localBus = new InMemoryExactBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
  });

  // No responder - should timeout
  await assert.rejects(
    () => router.invoke(
      'flowpad.tab-123/component.accepts.noResponse',
      {},
      100 // 100ms timeout
    ),
    /Local RPC timeout/
  );
});

test('FederationPeer.invoke: local invoke error handling', async () => {
  const localBus = new InMemoryFilterBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    instanceResolver: new TabInstanceResolver('flowpad.tab-123'),
  });

  // Set up a responder that returns an error
  // FP-21-3: Cmd routes to $inbox
  const browserTopic = 'component/$inbox';
  localBus.subscribe(browserTopic, (topic, payload) => {
    const request = JSON.parse(payload);
    const reply = {
      ok: false,
      error: { message: 'Something went wrong' },
    };
    localBus.publish(request.replyTo, JSON.stringify(reply));
  });

  // Invoke should throw with error message
  await assert.rejects(
    () => router.invoke(
      'flowpad.tab-123/component.accepts.failingMethod',
      {},
      1000
    ),
    /Something went wrong/
  );
});

test('FederationPeer.invoke: cross-realm requires backbone transport', async () => {
  const localBus = new InMemoryExactBroker();

  const router = new FederationPeer({
    localRealm: 'flowpad.tab-123',
    localBus,
    // No backbone provided
  });

  // Cross-realm invoke should fail without backbone transport
  await assert.rejects(
    () => router.invoke(
      'daemon/project.accepts.scan',
      {},
      1000
    ),
    /No backbone transport/
  );
});
