/**
 * Matrix Component Topology Scenarios
 *
 * Tests ALL possible locations where Matrix components can be relative to each other:
 *
 * TOPOLOGY 1: Same Realm, Same Process
 *   - Component A and B in same daemon
 *   - Communication via local message bus
 *
 * TOPOLOGY 2: Same Realm, Different Process (Browser ↔ Daemon)
 *   - Browser component talks to daemon component
 *   - Both in same realm, communication via local broker
 *
 * TOPOLOGY 3: Cross-Realm (Daemon A ↔ Daemon B)
 *   - Daemon in realm-alpha talks to daemon in realm-beta
 *   - Communication via backbone (NATS leaf nodes)
 *
 * TOPOLOGY 4: Cross-Realm Browser to Remote Daemon
 *   - Browser in realm-alpha talks to daemon in realm-beta
 *   - Browser → local daemon (gateway) → backbone → remote daemon
 *
 * TOPOLOGY 5: Supervision Across Realms
 *   - Parent in realm-alpha supervises child in realm-beta
 *   - $join/$join_ack flow across federation
 */

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

describe('Matrix Component Topology Scenarios', () => {

  // ===========================================================================
  // TOPOLOGY 1: Same Realm, Same Process
  // ===========================================================================

  describe('Topology 1: Same Realm, Same Process', () => {

    it('component A calls component B within same daemon (local bus only)', async () => {
      const REALM = 'local-realm';

      // Simulates the local bus inside a single daemon
      const localBus = new InMemoryFilterBroker();

      // Component B: Calculator service
      localBus.subscribe(`calculator.accepts.multiply`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { a: number; b: number };
        };
        const result = req.params.a * req.params.b;
        localBus.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: { product: result },
        }));
      });

      // Component A: Calls calculator
      let reply: unknown = null;
      const correlationId = 'local-call-123';
      const replyTopic = `_client.emits.reply-${correlationId}`;

      localBus.subscribe(replyTopic, (topic, payload) => {
        reply = JSON.parse(payload);
      });

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

      // Component A sends request to Component B
      localBus.publish(`calculator.accepts.multiply`, JSON.stringify({
        correlationId,
        replyTo: replyTopic,
        params: { a: 7, b: 6 },
      }));

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

      assert.ok(reply, 'Component A should receive reply from Component B');
      assert.deepStrictEqual(
        (reply as { result: { product: number } }).result,
        { product: 42 }
      );

      console.log('✓ Topology 1: Same realm/process - 7 × 6 = 42');
    });
  });

  // ===========================================================================
  // TOPOLOGY 2: Same Realm, Different Process (Browser ↔ Daemon)
  // ===========================================================================

  describe('Topology 2: Same Realm, Browser ↔ Daemon', () => {

    it('browser component calls daemon component via local broker', async () => {
      const REALM = 'my-app-realm';

      // Local broker shared between browser and daemon
      const localBroker = new InMemoryFilterBroker();

      // Daemon component: FileService
      localBroker.subscribe(`daemon.file.accepts.list`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { path: string };
        };
        // Simulated file listing
        localBroker.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: {
            files: ['README.md', 'package.json', 'src/'],
            path: req.params.path,
          },
        }));
      });

      // Browser component: File Explorer
      let browserReceivedReply: unknown = null;
      const correlationId = 'browser-call-456';
      const replyTopic = `_browser.explorer.emits.reply-${correlationId}`;

      localBroker.subscribe(replyTopic, (topic, payload) => {
        browserReceivedReply = JSON.parse(payload);
      });

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

      // Browser sends request to daemon
      localBroker.publish(`daemon.file.accepts.list`, JSON.stringify({
        correlationId,
        replyTo: replyTopic,
        params: { path: '/' },
      }));

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

      assert.ok(browserReceivedReply, 'Browser should receive reply from daemon');
      assert.deepStrictEqual(
        (browserReceivedReply as { result: { files: string[] } }).result.files,
        ['README.md', 'package.json', 'src/']
      );

      console.log('✓ Topology 2: Browser ↔ Daemon (same realm) - file listing');
    });
  });

  // ===========================================================================
  // TOPOLOGY 3: Cross-Realm Daemon to Daemon
  // ===========================================================================

  describe('Topology 3: Cross-Realm Daemon ↔ Daemon', () => {

    it('daemon in realm-alpha queries daemon in realm-beta', async () => {
      const REALM_ALPHA = 'company-a.matrix';
      const REALM_BETA = 'company-b.matrix';

      // Backbone (simulates HiveMQ)
      const backbone = new InMemoryFilterBroker();

      // Local buses for each daemon
      const localBusAlpha = new InMemoryFilterBroker();
      const localBusBeta = new InMemoryFilterBroker();

      // EdgeGateway for each realm
      const gatewayAlpha = new FederationPeer({
        localRealm: REALM_ALPHA,
        profile: WIRE_PROFILES.matrix3,
        backbone,
        localBus: localBusAlpha,
        internalTimeoutMs: 5000,
      });

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

      gatewayAlpha.start();
      gatewayBeta.start();

      // Daemon Beta: User Directory Service
      localBusBeta.subscribe(`directory.user.accepts.lookup`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { userId: string };
        };
        // Simulated user lookup
        localBusBeta.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: {
            userId: req.params.userId,
            name: 'Alice Smith',
            email: 'alice@company-b.com',
            realm: REALM_BETA,
          },
        }));
      });

      // Daemon Alpha: Receives reply
      let crossRealmReply: unknown = null;
      backbone.subscribe(getLegacyIngressTopic(REALM_ALPHA), (topic, payload) => {
        const parsed = JSON.parse(payload);
        if (parsed.ok !== undefined) {
          crossRealmReply = parsed;
        }
      });

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

      // Daemon Alpha requests user from Daemon Beta
      const request = createLegacyRequest({
        toRealm: REALM_BETA,
        toPath: 'directory.user.accepts.lookup',
        fromRealm: REALM_ALPHA,
        replyTo: '_daemon.client',
        correlationId: 'cross-daemon-789',
        body: { userId: 'user-123' },
      });

      backbone.publish(getLegacyIngressTopic(REALM_BETA), serializeLegacyEnvelope(request));

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

      assert.ok(crossRealmReply, 'Alpha should receive reply from Beta');
      assert.strictEqual(
        (crossRealmReply as { body: { name: string } }).body.name,
        'Alice Smith'
      );

      console.log('✓ Topology 3: Daemon(Alpha) → Daemon(Beta) - user lookup');

      gatewayAlpha.stop();
      gatewayBeta.stop();
    });
  });

  // ===========================================================================
  // TOPOLOGY 4: Cross-Realm Browser to Remote Daemon
  // ===========================================================================

  describe('Topology 4: Cross-Realm Browser → Remote Daemon', () => {

    it('browser in realm-alpha calls service in realm-beta via federation', async () => {
      const REALM_ALPHA = 'browser-user-realm';
      const REALM_BETA = 'backend-service-realm';

      // Infrastructure
      const backbone = new InMemoryFilterBroker();
      const localBusAlpha = new InMemoryFilterBroker();
      const localBusBeta = new InMemoryFilterBroker();

      // Both realms have EdgeGateways
      const gatewayAlpha = new FederationPeer({
        localRealm: REALM_ALPHA,
        profile: WIRE_PROFILES.matrix3,
        backbone,
        localBus: localBusAlpha,
        internalTimeoutMs: 5000,
      });

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

      gatewayAlpha.start();
      gatewayBeta.start();

      // Remote daemon (realm-beta): Weather service
      localBusBeta.subscribe(`weather.service.accepts.forecast`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { city: string };
        };
        localBusBeta.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: {
            city: req.params.city,
            forecast: 'Sunny',
            temperature: 72,
            unit: 'F',
          },
        }));
      });

      // Browser (realm-alpha): Weather widget
      // In real scenario, browser talks to local daemon which forwards via EdgeGateway
      // Here we simulate the full flow

      let browserReply: unknown = null;
      backbone.subscribe(getLegacyIngressTopic(REALM_ALPHA), (topic, payload) => {
        const parsed = JSON.parse(payload);
        if (parsed.ok !== undefined) {
          browserReply = parsed;
        }
      });

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

      // Browser's request routed through local EdgeGateway to remote realm
      const request = createLegacyRequest({
        toRealm: REALM_BETA,
        toPath: 'weather.service.accepts.forecast',
        fromRealm: REALM_ALPHA,
        replyTo: '_browser.widget.reply',
        correlationId: 'browser-to-remote-001',
        body: { city: 'Seattle' },
      });

      backbone.publish(getLegacyIngressTopic(REALM_BETA), serializeLegacyEnvelope(request));

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

      assert.ok(browserReply, 'Browser should receive cross-realm reply');
      assert.strictEqual(
        (browserReply as { body: { forecast: string } }).body.forecast,
        'Sunny'
      );

      console.log('✓ Topology 4: Browser(Alpha) → Remote Daemon(Beta) - weather forecast');

      gatewayAlpha.stop();
      gatewayBeta.stop();
    });
  });

  // ===========================================================================
  // TOPOLOGY 5: Supervision Across Realms
  // ===========================================================================

  describe('Topology 5: Cross-Realm Supervision', () => {

    it('parent in realm-alpha supervises child in realm-beta ($join flow)', async () => {
      const REALM_ALPHA = 'supervisor-realm';
      const REALM_BETA = 'worker-realm';

      const backbone = new InMemoryFilterBroker();
      const localBusAlpha = new InMemoryFilterBroker();
      const localBusBeta = new InMemoryFilterBroker();

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

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

      gatewayAlpha.start();
      gatewayBeta.start();

      // Parent component in Alpha: handles $join requests
      let joinRequestReceived = false;
      localBusAlpha.subscribe(`parent.supervisor.accepts.$join`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { childId: string; childRealm: string };
        };
        joinRequestReceived = true;

        // Acknowledge the join
        localBusAlpha.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: {
            status: 'joined',
            supervisorId: 'parent.supervisor',
            childId: req.params.childId,
          },
        }));
      });

      // Child sends $join from realm-beta to parent in realm-alpha
      let joinAckReceived: unknown = null;
      backbone.subscribe(getLegacyIngressTopic(REALM_BETA), (topic, payload) => {
        const parsed = JSON.parse(payload);
        if (parsed.ok !== undefined) {
          joinAckReceived = parsed;
        }
      });

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

      // Child component in Beta sends $join to parent in Alpha
      const joinRequest = createLegacyRequest({
        toRealm: REALM_ALPHA,
        toPath: 'parent.supervisor.accepts.$join',
        fromRealm: REALM_BETA,
        replyTo: '_child.worker.reply',
        correlationId: 'join-request-001',
        body: {
          childId: 'child.worker',
          childRealm: REALM_BETA,
          componentClass: 'WorkerComponent',
        },
      });

      backbone.publish(getLegacyIngressTopic(REALM_ALPHA), serializeLegacyEnvelope(joinRequest));

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

      assert.ok(joinRequestReceived, 'Parent should receive $join request');
      assert.ok(joinAckReceived, 'Child should receive $join_ack');
      assert.strictEqual(
        (joinAckReceived as { body: { status: string } }).body.status,
        'joined'
      );

      console.log('✓ Topology 5: Cross-realm supervision - $join flow');

      gatewayAlpha.stop();
      gatewayBeta.stop();
    });

    it('parent receives $exit notification from remote child', async () => {
      const REALM_ALPHA = 'supervisor-realm-2';
      const REALM_BETA = 'worker-realm-2';

      const backbone = new InMemoryFilterBroker();
      const localBusAlpha = new InMemoryFilterBroker();
      const localBusBeta = new InMemoryFilterBroker();

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

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

      gatewayAlpha.start();
      gatewayBeta.start();

      // Parent subscribes to $exit events from child
      let exitEventReceived = false;
      let exitReason: string | undefined;

      localBusAlpha.subscribe(`parent.supervisor.accepts.$exit`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { childId: string; reason: string };
        };
        exitEventReceived = true;
        exitReason = req.params.reason;

        // Acknowledge receipt
        localBusAlpha.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
        }));
      });

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

      // Child in Beta sends $exit to parent in Alpha
      const exitNotification = createLegacyRequest({
        toRealm: REALM_ALPHA,
        toPath: 'parent.supervisor.accepts.$exit',
        fromRealm: REALM_BETA,
        replyTo: '_child.worker.reply',
        correlationId: 'exit-notify-001',
        body: {
          childId: 'child.worker',
          reason: 'completed',
          result: { itemsProcessed: 100 },
        },
      });

      backbone.publish(getLegacyIngressTopic(REALM_ALPHA), serializeLegacyEnvelope(exitNotification));

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

      assert.ok(exitEventReceived, 'Parent should receive $exit notification');
      assert.strictEqual(exitReason, 'completed');

      console.log('✓ Topology 5: Cross-realm supervision - $exit notification');

      gatewayAlpha.stop();
      gatewayBeta.stop();
    });
  });

  // ===========================================================================
  // BONUS: Complete Flow - Browser → Local Daemon → Remote Daemon → Response
  // ===========================================================================

  describe('Complete Flow: Full Stack Federation', () => {

    it('browser request travels through full federation stack', async () => {
      const BROWSER_REALM = 'end-user-app';
      const LOCAL_DAEMON_REALM = 'local-proxy';
      const REMOTE_SERVICE_REALM = 'cloud-api';

      const backbone = new InMemoryFilterBroker();
      const localBusBrowser = new InMemoryFilterBroker();  // Browser's local connection
      const localBusLocalDaemon = new InMemoryFilterBroker();
      const localBusRemote = new InMemoryFilterBroker();

      // Gateway for each realm
      const gatewayBrowser = new FederationPeer({
        localRealm: BROWSER_REALM,
        profile: WIRE_PROFILES.matrix3,
        backbone,
        localBus: localBusBrowser,
        internalTimeoutMs: 5000,
      });

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

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

      gatewayBrowser.start();
      gatewayLocalDaemon.start();
      gatewayRemote.start();

      // Remote cloud service: AI processing
      localBusRemote.subscribe(`ai.service.accepts.analyze`, (topic, payload) => {
        const req = JSON.parse(payload) as {
          replyTo: string;
          correlationId: string;
          params: { text: string };
        };
        localBusRemote.publish(req.replyTo, JSON.stringify({
          ok: true,
          correlationId: req.correlationId,
          result: {
            sentiment: 'positive',
            confidence: 0.92,
            processedBy: REMOTE_SERVICE_REALM,
          },
        }));
      });

      // Track the response
      let finalResponse: unknown = null;
      backbone.subscribe(getLegacyIngressTopic(BROWSER_REALM), (topic, payload) => {
        const parsed = JSON.parse(payload);
        if (parsed.ok !== undefined) {
          finalResponse = parsed;
        }
      });

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

      // Browser initiates request to remote AI service
      const request = createLegacyRequest({
        toRealm: REMOTE_SERVICE_REALM,
        toPath: 'ai.service.accepts.analyze',
        fromRealm: BROWSER_REALM,
        replyTo: '_browser.app.reply',
        correlationId: 'full-stack-001',
        body: { text: 'Matrix federation is working great!' },
      });

      backbone.publish(getLegacyIngressTopic(REMOTE_SERVICE_REALM), serializeLegacyEnvelope(request));

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

      assert.ok(finalResponse, 'Browser should receive response from remote cloud service');
      assert.strictEqual(
        (finalResponse as { body: { sentiment: string } }).body.sentiment,
        'positive'
      );

      console.log('✓ Complete Flow: Browser → Local → Remote → Response (sentiment: positive)');

      gatewayBrowser.stop();
      gatewayLocalDaemon.stop();
      gatewayRemote.stop();
    });
  });
});
