/**
 * mount-migration.test.ts — Verifies daemon.* → direct mount migration.
 *
 * VISION.md Section 3: "Terminology: 'Runtime' Not 'Daemon'"
 * CONSTRAINTS.md Invariant 9: "No 'daemon' in architecture"
 *
 * System services mount directly under root (system.llm, code.files, cli)
 * rather than under daemon.* prefix.
 */
import { describe, it, before } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixActor } from '../../MatrixActor.js';
import { MatrixContext } from '../../../engine/core/MatrixContext.js';
import { InMemoryBroker } from '../../../transport/InMemoryBroker.js';
import { InMemoryTransport } from '../../../transport/InMemoryTransport.js';
import { TopicRouter } from '../../../engine/messaging/TopicRouter.js';
import type { IMatrixContext } from '../../../engine/core/IMatrixContext.js';
import type { MxEnvelope } from '../../../engine/messaging/MxEnvelope.js';
import { buildContext, TestActor, tick, unwrapReply } from './helpers.js';

describe('Mount migration — daemon.* → direct root mounts', () => {
  let broker: InMemoryBroker;
  let rootCtx: IMatrixContext;

  before(() => {
    broker = new InMemoryBroker();
    rootCtx = buildContext(broker);
  });

  it('1. system.llm reachable at direct mount path', async () => {
    class MockLlm extends MatrixActor {
      static accepts = { '$prompt': {} };
      async on$prompt(): Promise<Record<string, unknown>> { return { text: 'hello from system.llm' }; }
    }
    const actor = new MockLlm();
    const ctx = rootCtx.derive('system.llm');
    await actor.initialize(ctx, 'system.llm');
    await tick();

    // Send $ping to system.llm (not daemon.system.llm)
    let reply: unknown;
    rootCtx.transport.subscribe('$replies.llm1', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('system.llm'), {
      op: '$ping',
      payload: {},
      replyTo: '$replies.llm1',
      correlationId: 'llm1',
    } as MxEnvelope);
    await tick();
    assert.ok(reply, 'system.llm should respond to $ping');
    assert.equal((unwrapReply(reply) as { status?: string } | undefined)?.status, 'ok');
  });

  it('2. code.files reachable at direct mount path', async () => {
    class MockFiles extends MatrixActor {
      static accepts = { 'file.read': {} };
    }
    const actor = new MockFiles();
    const ctx = rootCtx.derive('code.files');
    await actor.initialize(ctx, 'code.files');
    await tick();

    let reply: unknown;
    rootCtx.transport.subscribe('$replies.files1', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('code.files'), {
      op: '$ping',
      payload: {},
      replyTo: '$replies.files1',
      correlationId: 'files1',
    } as MxEnvelope);
    await tick();
    assert.ok(reply, 'code.files should respond to $ping');
    assert.equal((unwrapReply(reply) as { status?: string } | undefined)?.status, 'ok');
  });

  it('3. cli reachable at direct mount path', async () => {
    class MockCli extends MatrixActor {
      static accepts = { 'cli.run': {} };
    }
    const actor = new MockCli();
    const ctx = rootCtx.derive('cli');
    await actor.initialize(ctx, 'cli');
    await tick();

    let reply: unknown;
    rootCtx.transport.subscribe('$replies.cli1', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('cli'), {
      op: '$ping',
      payload: {},
      replyTo: '$replies.cli1',
      correlationId: 'cli1',
    } as MxEnvelope);
    await tick();
    assert.ok(reply, 'cli should respond to $ping');
    assert.equal((unwrapReply(reply) as { status?: string } | undefined)?.status, 'ok');
  });

  it('4. $introspect on system.llm shows correct mount', async () => {
    let reply: unknown;
    rootCtx.transport.subscribe('$replies.intro1', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('system.llm'), {
      op: '$introspect',
      payload: {},
      replyTo: '$replies.intro1',
      correlationId: 'intro1',
    } as MxEnvelope);
    await tick();
    assert.ok(reply, 'Should get introspect reply');
    assert.equal((unwrapReply(reply) as { mount?: string } | undefined)?.mount, 'system.llm', 'Mount should be system.llm, not daemon.system.llm');
  });

  it('5. Membrane auth accepts system.* sender (not daemon.system.*)', async () => {
    const actor = new TestActor();
    const ctx = rootCtx.derive('target-actor');
    await actor.initialize(ctx, 'target-actor');
    await tick();

    // system.llm should be able to set membrane (it's a system sender)
    let reply: unknown;
    rootCtx.transport.subscribe('$replies.mem1', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('target-actor'), {
      op: '$membrane',
      payload: { action: 'set-local', source: '(effect membrane.allow op payload)' },
      from: 'system.llm',
      replyTo: '$replies.mem1',
      correlationId: 'mem1',
    } as MxEnvelope);
    await tick();
    const r = unwrapReply(reply) as { ok?: boolean } | undefined;
    assert.equal(r?.ok, true, 'system.llm sender should be authorized');
  });

  it('6. Membrane auth accepts cli sender (not daemon.cli)', async () => {
    const actor = new TestActor();
    const ctx = rootCtx.derive('target-actor-2');
    await actor.initialize(ctx, 'target-actor-2');
    await tick();

    let reply: unknown;
    rootCtx.transport.subscribe('$replies.mem2', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('target-actor-2'), {
      op: '$membrane',
      payload: { action: 'set-local', source: '(effect membrane.allow op payload)' },
      from: 'cli',
      replyTo: '$replies.mem2',
      correlationId: 'mem2',
    } as MxEnvelope);
    await tick();
    const r = unwrapReply(reply) as { ok?: boolean } | undefined;
    assert.equal(r?.ok, true, 'cli sender should be authorized');
  });

  it('7. Membrane auth still accepts legacy daemon.system.* sender (backward compat)', async () => {
    const actor = new TestActor();
    const ctx = rootCtx.derive('target-actor-3');
    await actor.initialize(ctx, 'target-actor-3');
    await tick();

    let reply: unknown;
    rootCtx.transport.subscribe('$replies.mem3', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('target-actor-3'), {
      op: '$membrane',
      payload: { action: 'set-local', source: '(effect membrane.allow op payload)' },
      from: 'daemon.system.llm',
      replyTo: '$replies.mem3',
      correlationId: 'mem3',
    } as MxEnvelope);
    await tick();
    const r = unwrapReply(reply) as { ok?: boolean } | undefined;
    assert.equal(r?.ok, true, 'Legacy daemon.system.* sender should still be authorized (backward compat)');
  });

  it('8. Membrane auth still accepts legacy daemon.cli sender (backward compat)', async () => {
    const actor = new TestActor();
    const ctx = rootCtx.derive('target-actor-4');
    await actor.initialize(ctx, 'target-actor-4');
    await tick();

    let reply: unknown;
    rootCtx.transport.subscribe('$replies.mem4', (p: unknown) => { reply = p; });
    rootCtx.transport.publish(TopicRouter.inbox('target-actor-4'), {
      op: '$membrane',
      payload: { action: 'set-local', source: '(effect membrane.allow op payload)' },
      from: 'daemon.cli',
      replyTo: '$replies.mem4',
      correlationId: 'mem4',
    } as MxEnvelope);
    await tick();
    const r = unwrapReply(reply) as { ok?: boolean } | undefined;
    assert.equal(r?.ok, true, 'Legacy daemon.cli sender should still be authorized (backward compat)');
  });
});
