/**
 * EdgeGateway Routing Integration Tests
 *
 * Tests the EdgeGateway's ability to route messages between
 * backbone and local bus using in-memory transports.
 */

import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import {
  FederationPeer, ExactInstanceResolver,
  WIRE_PROFILES,
  InMemoryFilterBroker,
  getLegacyIngressTopic,
} from '@open-matrix/federation';

describe('EdgeGateway Routing', () => {

  it('routes inbound request from backbone to local bus', async () => {
    const REALM = 'test-realm';

    // Create in-memory brokers
    const backbone = new InMemoryFilterBroker();
    const localBus = new InMemoryFilterBroker();

    // Create EdgeGateway
    const gateway = new FederationPeer({
      localRealm: REALM,
      profile: WIRE_PROFILES.matrix3,
      backbone,
      localBus,
    });

    gateway.start();

    // Subscribe to internal topic on local bus (simulating a handler)
    // Internal topic format: <toPath> (realm is a transport concern)
    let receivedRequest: unknown = null;
    localBus.subscribe(`test.component.accepts.ping`, (topic, payload) => {
      receivedRequest = JSON.parse(payload);
    });

    // Allow subscriptions to propagate
    await new Promise(r => setTimeout(r, 50));

    // Send request to backbone mailbox using LEGACY format (flat fields)
    const request = {
      toRealm: REALM,
      toPath: 'test.component.accepts.ping',
      fromRealm: 'client-realm',
      replyTo: '_reply',  // just the path, not an object
      correlationId: 'test-123',
      body: { message: 'hello' },
    };

    backbone.publish(getLegacyIngressTopic(REALM), JSON.stringify(request));

    // Wait for message to be routed
    await new Promise(r => setTimeout(r, 100));

    assert.ok(receivedRequest, 'Should receive request on local bus');
    assert.strictEqual((receivedRequest as { correlationId: string }).correlationId, 'test-123');

    gateway.stop();
  });

  it('routes reply from local bus back to backbone', async () => {
    const REALM = 'test-realm';
    const CLIENT_REALM = 'client-realm';

    const backbone = new InMemoryFilterBroker();
    const localBus = new InMemoryFilterBroker();

    const gateway = new FederationPeer({
      localRealm: REALM,
      profile: WIRE_PROFILES.matrix3,
      backbone,
      localBus,
      internalTimeoutMs: 5000,
    });

    gateway.start();

    // Subscribe to client's mailbox on backbone (where reply should go)
    let receivedReply: unknown = null;
    backbone.subscribe(getLegacyIngressTopic(CLIENT_REALM), (topic, payload) => {
      receivedReply = JSON.parse(payload);
    });

    // Set up handler on local bus that will reply
    // Internal topic format: <toPath> (realm is a transport concern)
    localBus.subscribe(`test.component.accepts.echo`, (topic, payload) => {
      const req = JSON.parse(payload) as { replyTo: string; correlationId: string; params: unknown };
      // Send reply to the replyTo topic
      localBus.publish(req.replyTo, JSON.stringify({
        ok: true,
        correlationId: req.correlationId,
        result: { echo: 'hello back' },
      }));
    });

    await new Promise(r => setTimeout(r, 50));

    // Send request from client using LEGACY format (flat fields)
    const request = {
      toRealm: REALM,
      toPath: 'test.component.accepts.echo',
      fromRealm: CLIENT_REALM,
      replyTo: '_reply',  // just the path
      correlationId: 'echo-456',
      body: { message: 'hello' },
    };

    backbone.publish(getLegacyIngressTopic(REALM), JSON.stringify(request));

    // Wait for round-trip
    await new Promise(r => setTimeout(r, 200));

    assert.ok(receivedReply, 'Should receive reply on backbone');

    gateway.stop();
  });

  it('uses exact mailbox topic (no wildcards)', async () => {
    const REALM = 'my-realm';

    const backbone = new InMemoryFilterBroker();
    const localBus = new InMemoryFilterBroker();

    // Track what topics are subscribed
    const subscribedTopics: string[] = [];
    const originalSubscribe = backbone.subscribe.bind(backbone);
    backbone.subscribe = (topic: string, handler: (t: string, p: string) => void) => {
      subscribedTopics.push(topic);
      return originalSubscribe(topic, handler);
    };

    const gateway = new FederationPeer({
      localRealm: REALM,
      profile: WIRE_PROFILES.matrix3,
      backbone,
      localBus,
    });

    gateway.start();

    // Verify subscribed topic is exact mailbox, not wildcard
    const mailboxTopic = getLegacyIngressTopic(REALM);
    assert.ok(
      subscribedTopics.includes(mailboxTopic),
      `Should subscribe to exact mailbox: ${mailboxTopic}`
    );
    assert.ok(
      !subscribedTopics.some(t => t.includes('#') || t.includes('+')),
      'Should NOT use wildcards on backbone subscription'
    );

    gateway.stop();
  });

  it('metrics track inbound requests', async () => {
    const REALM = 'metrics-realm';

    const backbone = new InMemoryFilterBroker();
    const localBus = new InMemoryFilterBroker();

    const gateway = new FederationPeer({
      localRealm: REALM,
      profile: WIRE_PROFILES.matrix3,
      backbone,
      localBus,
    });

    gateway.start();

    // Set up a handler so request completes
    // Internal topic format: <toPath> (realm is a transport concern)
    localBus.subscribe(`test.accepts.cmd`, (topic, payload) => {
      const req = JSON.parse(payload) as { replyTo: string; correlationId: string };
      localBus.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId }));
    });

    await new Promise(r => setTimeout(r, 50));

    const initialMetrics = gateway.getMetrics();

    // Send a request using LEGACY format (flat fields)
    const request = {
      toRealm: REALM,
      toPath: 'test.accepts.cmd',
      fromRealm: 'other',
      replyTo: '_reply',  // just the path
      correlationId: 'metrics-1',
      body: {},
    };

    backbone.publish(getLegacyIngressTopic(REALM), JSON.stringify(request));

    await new Promise(r => setTimeout(r, 200));

    const finalMetrics = gateway.getMetrics();

    assert.ok(
      finalMetrics.inboundRequests > initialMetrics.inboundRequests,
      'Inbound requests should increase'
    );

    gateway.stop();
  });
});
