import 'reflect-metadata';
import { Global, Module } from '@nestjs/common';
import { Test, type TestingModule } from '@nestjs/testing';
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
import type { HarnessAdapter, HarnessConversationService } from '@mosaicstack/types';
import { AgentService } from '../agent/agent.service.js';
import { AuthGuard } from '../auth/auth.guard.js';
import { CommandsModule } from '../commands/commands.module.js';
import { HarnessModule } from '../harness/harness.module.js';
import { ChatModule } from './chat.module.js';
import { ChatGateway } from './chat.gateway.js';
import { HarnessRegistry } from '../harness/harness.registry.js';
import {
  HARNESS_CONVERSATION_SERVICE,
  HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
  HARNESS_REGISTRY,
  type HarnessConversationServiceBinding,
} from '../harness/harness.tokens.js';
import { ChatRuntimeRouter } from './chat-runtime-router.js';
import {
  ChatRuntimeUnavailableError,
  ownConversation,
  type ChatRuntime,
  type ChatRuntimeMode,
  type LegacyEmbeddedChatPort,
  type LegacyRuntimeStream,
  type LegacySessionPresentation,
  type LegacySocketTurnLease,
  type OwnedConversationContext,
} from './chat-runtime.js';
import { AppModule } from '../app.module.js';
import { ProviderService } from '../agent/provider.service.js';

/**
 * Task Five, Step One (router). Proves the `ChatRuntimeRouter` resolves exactly one
 * runtime by mode, fails closed at init when `pi-rpc` preconditions are unmet, and
 * never downgrades `pi-rpc` to embedded execution. Red-first: the router is an
 * unimplemented stub, so every behavioural assertion below fails until Step Three.
 */

const embedded: ChatRuntime = { kind: 'embedded' };
const harness: ChatRuntime = { kind: 'harness' };

/** A structurally-complete, non-sentinel conversation service. Its methods are never invoked here. */
const boundConversationService = {
  attach: () => Promise.reject(new Error('unused')),
  detach: () => Promise.reject(new Error('unused')),
  send: () => Promise.reject(new Error('unused')),

  subscribeFrom: async function* () {
    throw new Error('unused');
  },
} as unknown as HarnessConversationService;

function registryWith(adapterIds: readonly string[]): HarnessRegistry {
  const registry = new HarnessRegistry();
  for (const id of adapterIds) {
    registry.register({
      id,
      describe: () => Promise.reject(new Error('unused')),
      catalog: () => Promise.reject(new Error('unused')),
      create: () => Promise.reject(new Error('unused')),
      resume: () => Promise.reject(new Error('unused')),
    } as HarnessAdapter);
  }
  return registry;
}

function buildRouter(
  mode: ChatRuntimeMode,
  opts: { adapters: readonly string[]; service: HarnessConversationServiceBinding },
): ChatRuntimeRouter {
  return new ChatRuntimeRouter(registryWith(opts.adapters), opts.service, embedded, harness, mode);
}

/**
 * Tear down a module that was deliberately driven to a fail-closed init.
 * `NestApplicationContext.close()` re-awaits the module's `initializationPromise` before disposing
 * (nest-application-context.js:127); when `init()` rejected, that await re-throws the SAME typed
 * startup error, this time into teardown. Each caller here has already captured and asserted that
 * exact `ChatRuntimeUnavailableError` via `initError`, so the re-throw is expected teardown noise —
 * swallow ONLY that error, and surface anything else so a genuine teardown fault still fails loudly.
 */
async function closeIgnoringFailedInit(moduleRef: TestingModule): Promise<void> {
  await moduleRef.close().catch((err: unknown) => {
    if (err instanceof ChatRuntimeUnavailableError) return;
    throw err;
  });
}

describe('ChatRuntimeRouter', () => {
  it('resolves only the harness runtime in pi-rpc mode when pi adapter and conversation service are present', () => {
    const router = buildRouter('pi-rpc', {
      adapters: ['pi'],
      service: boundConversationService,
    });

    expect(() => router.onModuleInit()).not.toThrow();
    expect(router.active).toBe(harness);
    expect(router.active.kind).toBe('harness');
  });

  it('resolves only the embedded runtime in legacy mode and skips the pi preconditions', () => {
    // Empty registry + unavailable service: legacy must ignore both and still start.
    const router = buildRouter('legacy', {
      adapters: [],
      service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });

    expect(() => router.onModuleInit()).not.toThrow();
    expect(router.active).toBe(embedded);
    expect(router.active.kind).toBe('embedded');
  });

  it('fails closed at init when pi-rpc mode has no registered pi adapter', () => {
    const router = buildRouter('pi-rpc', {
      adapters: [],
      service: boundConversationService,
    });

    expect(() => router.onModuleInit()).toThrow(ChatRuntimeUnavailableError);
    try {
      router.onModuleInit();
      expect.unreachable('onModuleInit must throw when the pi adapter is absent');
    } catch (err) {
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    }
  });

  it('fails closed at init when pi-rpc mode has the unavailable conversation-service sentinel', () => {
    const router = buildRouter('pi-rpc', {
      adapters: ['pi'],
      service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });

    try {
      router.onModuleInit();
      expect.unreachable('onModuleInit must throw when the conversation service is unbound');
    } catch (err) {
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    }
  });

  it('never falls back to embedded execution when pi-rpc preconditions are unmet', () => {
    const router = buildRouter('pi-rpc', {
      adapters: [],
      service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });

    expect(() => router.onModuleInit()).toThrow(ChatRuntimeUnavailableError);
    // A failed pi-rpc init must not silently expose the embedded runtime.
    expect(() => router.active).toThrow();
    let leaked: ChatRuntime | undefined;
    try {
      leaked = router.active;
    } catch {
      leaked = undefined;
    }
    expect(leaked).not.toBe(embedded);
  });

  it('exposes only fixed, browser-safe failure text (no raw provider or exception detail)', () => {
    const router = buildRouter('pi-rpc', {
      adapters: [],
      service: boundConversationService,
    });

    try {
      router.onModuleInit();
      expect.unreachable('onModuleInit must throw');
    } catch (err) {
      const message = (err as ChatRuntimeUnavailableError).message;
      expect(message).toBe(
        'The pi-rpc chat runtime is unavailable: no "pi" harness adapter is registered.',
      );
      expect(message).not.toMatch(/Error:|\bat \b|node_modules|Symbol\(/);
    }
  });
});

/**
 * Task Five, Step Three — legacy port operations fail closed under pi-rpc (direct valid-input).
 *
 * The unit suite above constructs the router but never invokes a legacy port operation, so the
 * six per-operation inner `if (this.mode === 'pi-rpc')` guards are unexercised — a mutation that
 * deletes one of them SURVIVES for lack of a test that drives that operation. This group closes
 * that gap the right way: it drives each of the six operations DIRECTLY, in pi-rpc mode, with a
 * valid branded {@link OwnedConversationContext} and valid input, against a recording embedded
 * stub whose method returns a distinguishable `ok:true` success and increments a per-op counter.
 *
 * For each operation:
 *  - pi-rpc test asserts the exact frozen `{ ok:false, code:'runtime_unsupported', retryable:false }`
 *    result AND that the embedded stub was touched zero times (no effects);
 *  - the paired legacy test proves that same stub method IS reached and returns its distinguishable
 *    success when the mode does not refuse — so the pi-rpc zero-invocation assertion is meaningful,
 *    not vacuously true because the stub could never be called.
 *
 * Deleting ONLY one operation's inner guard makes THAT operation's pi-rpc test behaviorally RED
 * (the router returns the embedded `ok:true` value and records the call), with every outer guard
 * and the other five inner guards intact. `next` is untouched; nothing here changes production.
 */
describe('ChatRuntimeRouter — legacy port ops fail closed under pi-rpc (Task Five, Step Three)', () => {
  const RUNTIME_UNSUPPORTED = {
    ok: false,
    code: 'runtime_unsupported',
    retryable: false,
  } as const;

  const PRESENTATION: LegacySessionPresentation = {
    provider: 'embedded-provider',
    modelId: 'embedded-model',
    thinkingLevel: 'low',
    availableThinkingLevels: ['low', 'high'],
  };

  const stream: LegacyRuntimeStream = {
    channelId: 'websocket:test-socket',
    onEvent: () => {},
  };

  const ctx = (): OwnedConversationContext =>
    ownConversation('conversation-1', { userId: 'user-1', tenantId: 'tenant-1' });

  /**
   * Per-operation invocation counters with declared keys (not an index signature) so each
   * `calls.<op>` is definitely `number` under `noUncheckedIndexedAccess`.
   */
  type LegacyPortCallCounts = {
    completeLegacyRestTurn: number;
    prepareLegacySocketTurn: number;
    setLegacyThinking: number;
    abortLegacyTurn: number;
    applyLegacyModelOverride: number;
    readLegacySessionPresentation: number;
    dispatchVerifiedDiscordIngress: number;
  };

  /**
   * An embedded port that records every invocation and returns a distinguishable `ok:true`
   * value per operation. If a router op reaches it (its guard removed), both the recorded call
   * count and the returned `ok:true` value diverge from the frozen `runtime_unsupported` result.
   */
  function recordingEmbeddedPort(): {
    port: ChatRuntime & LegacyEmbeddedChatPort;
    calls: LegacyPortCallCounts;
  } {
    const calls: LegacyPortCallCounts = {
      completeLegacyRestTurn: 0,
      prepareLegacySocketTurn: 0,
      setLegacyThinking: 0,
      abortLegacyTurn: 0,
      applyLegacyModelOverride: 0,
      readLegacySessionPresentation: 0,
      dispatchVerifiedDiscordIngress: 0,
    };
    const lease: LegacySocketTurnLease = {
      presentation: PRESENTATION,
      dispatch: () => Promise.resolve({ ok: true, value: undefined }),
      dispose: () => Promise.resolve(),
    };
    const port: ChatRuntime & LegacyEmbeddedChatPort = {
      kind: 'embedded',
      completeLegacyRestTurn: () => {
        calls.completeLegacyRestTurn += 1;
        return Promise.resolve({
          ok: true,
          value: { text: 'EMBEDDED-REST', presentation: PRESENTATION },
        });
      },
      prepareLegacySocketTurn: () => {
        calls.prepareLegacySocketTurn += 1;
        return Promise.resolve({ ok: true, value: lease });
      },
      setLegacyThinking: () => {
        calls.setLegacyThinking += 1;
        return { ok: true, value: PRESENTATION };
      },
      abortLegacyTurn: () => {
        calls.abortLegacyTurn += 1;
        return Promise.resolve({ ok: true, value: undefined });
      },
      applyLegacyModelOverride: () => {
        calls.applyLegacyModelOverride += 1;
        return { ok: true, value: PRESENTATION };
      },
      readLegacySessionPresentation: () => {
        calls.readLegacySessionPresentation += 1;
        return { ok: true, value: PRESENTATION };
      },
      dispatchVerifiedDiscordIngress: () => {
        calls.dispatchVerifiedDiscordIngress += 1;
        return Promise.resolve({
          ok: true,
          value: {
            presentation: PRESENTATION,
            dispatch: () => Promise.resolve({ ok: true, value: undefined }),
            dispose: () => Promise.resolve(),
          },
        });
      },
    };
    return { port, calls };
  }

  function piRouter(port: ChatRuntime & LegacyEmbeddedChatPort): ChatRuntimeRouter {
    return new ChatRuntimeRouter(
      registryWith(['pi']),
      boundConversationService,
      port,
      harness,
      'pi-rpc',
    );
  }
  function legacyRouter(port: ChatRuntime & LegacyEmbeddedChatPort): ChatRuntimeRouter {
    return new ChatRuntimeRouter(
      registryWith([]),
      boundConversationService,
      port,
      harness,
      'legacy',
    );
  }

  // completeLegacyRestTurn ---------------------------------------------------
  it('completeLegacyRestTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await piRouter(port).completeLegacyRestTurn(ctx(), { content: 'hello' });
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.completeLegacyRestTurn).toBe(0);
  });
  it('completeLegacyRestTurn delegates to embedded under legacy (guard is the sole gate)', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await legacyRouter(port).completeLegacyRestTurn(ctx(), { content: 'hello' });
    expect(result.ok).toBe(true);
    expect(calls.completeLegacyRestTurn).toBe(1);
  });

  // prepareLegacySocketTurn --------------------------------------------------
  it('prepareLegacySocketTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await piRouter(port).prepareLegacySocketTurn(
      ctx(),
      { content: 'hello' },
      stream,
    );
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.prepareLegacySocketTurn).toBe(0);
  });
  it('prepareLegacySocketTurn delegates to embedded under legacy (guard is the sole gate)', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await legacyRouter(port).prepareLegacySocketTurn(
      ctx(),
      { content: 'hello' },
      stream,
    );
    expect(result.ok).toBe(true);
    expect(calls.prepareLegacySocketTurn).toBe(1);
  });

  // setLegacyThinking (sync) -------------------------------------------------
  it('setLegacyThinking refuses with runtime_unsupported and never touches embedded under pi-rpc', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = piRouter(port).setLegacyThinking(ctx(), 'high');
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.setLegacyThinking).toBe(0);
  });
  it('setLegacyThinking delegates to embedded under legacy (guard is the sole gate)', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = legacyRouter(port).setLegacyThinking(ctx(), 'high');
    expect(result.ok).toBe(true);
    expect(calls.setLegacyThinking).toBe(1);
  });

  // abortLegacyTurn ----------------------------------------------------------
  it('abortLegacyTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await piRouter(port).abortLegacyTurn(ctx());
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.abortLegacyTurn).toBe(0);
  });
  it('abortLegacyTurn delegates to embedded under legacy (guard is the sole gate)', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = await legacyRouter(port).abortLegacyTurn(ctx());
    expect(result.ok).toBe(true);
    expect(calls.abortLegacyTurn).toBe(1);
  });

  // applyLegacyModelOverride (sync) ------------------------------------------
  it('applyLegacyModelOverride refuses with runtime_unsupported and never touches embedded under pi-rpc', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = piRouter(port).applyLegacyModelOverride(ctx(), 'model-x');
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.applyLegacyModelOverride).toBe(0);
  });
  it('applyLegacyModelOverride delegates to embedded under legacy (guard is the sole gate)', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = legacyRouter(port).applyLegacyModelOverride(ctx(), 'model-x');
    expect(result.ok).toBe(true);
    expect(calls.applyLegacyModelOverride).toBe(1);
  });

  // readLegacySessionPresentation (sync) -------------------------------------
  it('readLegacySessionPresentation refuses with runtime_unsupported and never touches embedded under pi-rpc', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = piRouter(port).readLegacySessionPresentation(ctx());
    expect(result).toEqual(RUNTIME_UNSUPPORTED);
    expect(calls.readLegacySessionPresentation).toBe(0);
  });
  it('readLegacySessionPresentation delegates to embedded under legacy (guard is the sole gate)', () => {
    const { port, calls } = recordingEmbeddedPort();
    const result = legacyRouter(port).readLegacySessionPresentation(ctx());
    expect(result.ok).toBe(true);
    expect(calls.readLegacySessionPresentation).toBe(1);
  });

  // dispatchVerifiedDiscordIngress delegates in BOTH modes (embedded-only, no guard) ---------
  it('dispatchVerifiedDiscordIngress delegates to embedded under pi-rpc (embedded-only, no mode guard)', async () => {
    const { port, calls } = recordingEmbeddedPort();
    const discordCtx = ctx() as unknown as Parameters<
      ChatRuntimeRouter['dispatchVerifiedDiscordIngress']
    >[0];
    const result = await piRouter(port).dispatchVerifiedDiscordIngress(discordCtx, stream);
    expect(result.ok).toBe(true);
    expect(calls.dispatchVerifiedDiscordIngress).toBe(1);
  });
});

/**
 * Task Five, Step Two — group 1 (real Nest module-graph readiness).
 *
 * The unit suite above constructs the router directly. This group drives the SAME contract
 * through a real NestJS graph: it imports the production `HarnessModule` (the proven-booting
 * idiom from harness.controller.spec.ts) so the router resolves the REAL, empty `HarnessRegistry`
 * via the real `HARNESS_REGISTRY` token, then runs the router's `OnModuleInit` through the Nest
 * lifecycle (`moduleRef.init()`). Red-first: the router is an unimplemented stub whose
 * `onModuleInit` throws a generic Error, so:
 *   - readiness cases fail because the graph never comes up (init rejects), and
 *   - fail-closed cases fail because a generic stub throw is NOT the SPECIFIC typed
 *     `ChatRuntimeUnavailableError` (reason/code) the contract demands — a stub that
 *     "throws anything" cannot mask these greens.
 * The router is NOT wired into a production module yet, so it is provided here via a factory
 * over the real registry token. Importing the real `ChatModule` bare is deliberately avoided:
 * it injects `AgentService` without importing `AgentModule`, so its graph fails to RESOLVE — a
 * collection/DI error, not a behavioural red. `next` is untouched; nothing here implements the router.
 */
describe('ChatRuntimeRouter — real Nest module-graph readiness (Task Five, Step Two group 1)', () => {
  async function bootRouterGraph(
    mode: ChatRuntimeMode,
    opts: { adapters: readonly string[]; service: HarnessConversationServiceBinding },
  ) {
    const moduleRef = await Test.createTestingModule({
      imports: [HarnessModule],
      providers: [
        {
          provide: ChatRuntimeRouter,
          useFactory: (registry: HarnessRegistry) =>
            new ChatRuntimeRouter(registry, opts.service, embedded, harness, mode),
          inject: [HARNESS_REGISTRY],
        },
      ],
    })
      // The imported HarnessModule's controllers reference AuthGuard (an HTTP-only concern,
      // never exercised here); stub it so the graph resolves. The registry is NOT overridden —
      // group 1 asserts against the genuine production HarnessRegistry.
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true })
      .compile();

    // Resolve the production registry singleton and register the requested adapters ON IT, so
    // the router (which injects the same singleton) sees them when its lifecycle hook runs.
    const registry = moduleRef.get<HarnessRegistry>(HARNESS_REGISTRY, { strict: false });
    for (const id of opts.adapters) {
      registry.register({
        id,
        describe: () => Promise.reject(new Error('unused')),
        catalog: () => Promise.reject(new Error('unused')),
        create: () => Promise.reject(new Error('unused')),
        resume: () => Promise.reject(new Error('unused')),
      } as HarnessAdapter);
    }
    return moduleRef;
  }

  // Capture an init rejection without letting a resolved init masquerade as success.
  const initError = (moduleRef: { init(): Promise<unknown> }): Promise<unknown> =>
    moduleRef.init().then(
      () => new Error('module init resolved but the contract requires it to reject'),
      (err: unknown) => err,
    );

  it('brings the graph up and resolves only the harness runtime in pi-rpc mode (pi adapter + bound service)', async () => {
    const moduleRef = await bootRouterGraph('pi-rpc', {
      adapters: ['pi'],
      service: boundConversationService,
    });
    try {
      await moduleRef.init();
      const router = moduleRef.get(ChatRuntimeRouter, { strict: false });
      expect(router.active).toBe(harness);
      expect(router.active.kind).toBe('harness');
    } finally {
      await moduleRef.close();
    }
  });

  it('brings the graph up in legacy mode over the REAL empty HarnessRegistry and resolves only the embedded runtime', async () => {
    const moduleRef = await bootRouterGraph('legacy', {
      adapters: [],
      service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });
    try {
      // Defense-in-depth: the production module wires the genuine registry, empty by default —
      // guards against a test-double registry silently satisfying the readiness check.
      const registry = moduleRef.get<HarnessRegistry>(HARNESS_REGISTRY, { strict: false });
      expect(registry).toBeInstanceOf(HarnessRegistry);
      expect(registry.list()).toHaveLength(0);

      await moduleRef.init();
      const router = moduleRef.get(ChatRuntimeRouter, { strict: false });
      expect(router.active).toBe(embedded);
      expect(router.active.kind).toBe('embedded');
    } finally {
      await moduleRef.close();
    }
  });

  it('fails closed at module init when pi-rpc mode has no registered pi adapter (specific typed error, not a stub throw)', async () => {
    const moduleRef = await bootRouterGraph('pi-rpc', {
      adapters: [],
      service: boundConversationService,
    });
    try {
      const err = await initError(moduleRef);
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    } finally {
      await closeIgnoringFailedInit(moduleRef);
    }
  });

  it('fails closed at module init when pi-rpc mode has the unavailable conversation-service sentinel', async () => {
    const moduleRef = await bootRouterGraph('pi-rpc', {
      adapters: ['pi'],
      service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });
    try {
      const err = await initError(moduleRef);
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    } finally {
      await closeIgnoringFailedInit(moduleRef);
    }
  });

  it('surfaces only fixed, browser-safe failure text when the graph fails closed (no stub/exception detail)', async () => {
    const moduleRef = await bootRouterGraph('pi-rpc', {
      adapters: [],
      service: boundConversationService,
    });
    try {
      const err = await initError(moduleRef);
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      const message = (err as ChatRuntimeUnavailableError).message;
      expect(message).toBe(
        'The pi-rpc chat runtime is unavailable: no "pi" harness adapter is registered.',
      );
      expect(message).not.toMatch(/Error:|\bat \b|node_modules|Symbol\(|not implemented/);
    } finally {
      await closeIgnoringFailedInit(moduleRef);
    }
  });
});

/**
 * Task Five, Step Two — group 1b (production ChatModule wiring, declaration proof).
 *
 * Correction #1 (Scrappy fe3e02) asked for a red that imports the real `ChatModule` and calls
 * `module.init()`. Investigated and found impractical/masking-prone: `ChatModule` provides
 * `ChatGateway`, whose 10-argument constructor injects app-global providers (AgentService, AUTH,
 * BRAIN, RoutingEngineService) plus the Commands/GC/Mcp/Reload subsystems across a forwardRef
 * cycle. Booting it in isolation is a full-app integration boot — "override only unrelated
 * dependencies" balloons into faking ~4 subsystems, and `overrideProvider` cannot even grant the
 * cross-module export-scope visibility ChatGateway needs (probe: `ChatGateway` unresolved at
 * `CommandExecutorService`). That is exactly the STOP-and-return branch of the directive.
 *
 * The faithful, unmaskable cover instead of a fragile boot: read the PRODUCTION `ChatModule`'s own
 * Nest `@Module` metadata to prove it DECLARES the exclusive router provider and imports the real
 * `HarnessModule` (the genuine registry source). This inspects the actual module object — not
 * source text, not a test factory — so nothing can mask it. Group 1 above separately proves the
 * router RESOLVES against the real, empty `HarnessRegistry` through the Nest lifecycle; the union
 * of the two covers "the router is wired through ChatModule to the real registry" without the
 * impractical single-graph boot. RED today (ChatModule provides only ChatGateway and imports only
 * CommandsModule); GREEN once Step Three registers the router and imports HarnessModule.
 */
describe('ChatModule production wiring (Task Five, Step Two group 1b — declaration proof)', () => {
  // Unwrap a forwardRef(() => Module) import to the module it references; pass others through.
  const resolveImport = (imp: unknown): unknown =>
    imp &&
    typeof imp === 'object' &&
    typeof (imp as { forwardRef?: unknown }).forwardRef === 'function'
      ? (imp as { forwardRef: () => unknown }).forwardRef()
      : imp;

  // A provider entry is either a class (shorthand) or a { provide, ... } object; take its token.
  const providerToken = (provider: unknown): unknown =>
    typeof provider === 'function' ? provider : (provider as { provide?: unknown })?.provide;

  it('declares the exclusive ChatRuntimeRouter as a provider on the production ChatModule', () => {
    const providers: unknown[] = Reflect.getMetadata('providers', ChatModule) ?? [];
    expect(providers.map(providerToken)).toContain(ChatRuntimeRouter);
  });

  it('imports the real HarnessModule into the production ChatModule (registry source, not a test double)', () => {
    const imports: unknown[] = Reflect.getMetadata('imports', ChatModule) ?? [];
    expect(imports.map(resolveImport)).toContain(HarnessModule);
  });
});

/**
 * Task Five, Step Two — group 1c (bounded real-`ChatModule` boot).
 *
 * Scrappy adjudication d67d2b (option c): boot the ACTUAL production `ChatModule` as the SUT and
 * assert the exclusive router resolves THROUGH it — the single-graph proof group 1 (router over the
 * real registry) and group 1b (production-module metadata) each cover only a half of. The heavy,
 * UNRELATED cycle is the only thing bounded away, per the established isolation pattern in
 * `apps/gateway/src/agent/hermes-runtime-reachability.e2e.test.ts`:
 *   - `CommandsModule` (drags the Commands <-> Reload <-> Chat forwardRef cycle plus GC/Mcp/queue)
 *     is replaced wholesale with an empty module via `.overrideModule(...).useModule(...)`;
 *   - `ChatGateway` (10-arg constructor, an HTTP/socket concern never exercised here) is replaced
 *     with an inert value;
 *   - the sole legacy-controller dependency, `AgentService`, is supplied by a tiny `@Global()` stub;
 *   - the HTTP-only `AuthGuard` is stubbed.
 * Nothing about the router, `HarnessModule`, the registry, or the conversation-service binding is
 * faked in the production-legacy case — those are retrieved from the REAL `ChatModule` graph. Mode
 * is driven only through the production `CHAT_HARNESS_RUNTIME` env contract (`resolveChatRuntimeMode`).
 *
 * Red-first: today `ChatModule` neither imports `HarnessModule` nor provides `ChatRuntimeRouter`, so
 * the booted graph contains no router/registry/conversation-service tokens. `init()` may resolve
 * (there is no router lifecycle hook yet to reject), so every case fails on the MISSING actual
 * router/registry/service wiring — not on unrelated DI, which is bounded away. GREEN at Step Three
 * once `ChatModule` imports `HarnessModule`, provides the exclusive router, and binds the
 * conversation-service token (defaulting to the unavailable sentinel).
 */
describe('ChatModule bounded real boot (Task Five, Step Two group 1c)', () => {
  // The unrelated heavy cycle, replaced wholesale — not stubbed provider-by-provider.
  @Module({})
  class EmptyCommandsModule {}

  // The ONLY genuine legacy dependency of the real ChatController, supplied inertly and globally so
  // the pre-refactor controller instantiates without dragging AgentModule into the graph.
  @Global()
  @Module({
    providers: [{ provide: AgentService, useValue: {} }],
    exports: [AgentService],
  })
  class LegacyControllerDepsModule {}

  const ORIGINAL_RUNTIME_ENV = process.env['CHAT_HARNESS_RUNTIME'];
  afterEach(() => {
    if (ORIGINAL_RUNTIME_ENV === undefined) delete process.env['CHAT_HARNESS_RUNTIME'];
    else process.env['CHAT_HARNESS_RUNTIME'] = ORIGINAL_RUNTIME_ENV;
  });

  /**
   * Boot the real ChatModule with only the unrelated cycle bounded away. `mode` is set through the
   * genuine production env contract before providers instantiate. The optional overrides replace
   * the registry / conversation-service the router injects, exercising the pi-rpc precondition
   * branches through the ACTUAL module (they are no-ops today because those tokens are not yet in
   * the graph — which is exactly why the router-retrieval assertions go red).
   */
  async function bootChatModule(
    mode: ChatRuntimeMode,
    overrides: {
      registryAdapters?: readonly string[];
      conversationService?: HarnessConversationServiceBinding;
    } = {},
  ): Promise<TestingModule> {
    if (mode === 'pi-rpc') process.env['CHAT_HARNESS_RUNTIME'] = 'pi-rpc';
    else delete process.env['CHAT_HARNESS_RUNTIME'];

    let builder = Test.createTestingModule({
      imports: [LegacyControllerDepsModule, ChatModule],
    })
      .overrideModule(CommandsModule)
      .useModule(EmptyCommandsModule)
      .overrideProvider(ChatGateway)
      .useValue({})
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true });

    if (overrides.registryAdapters) {
      builder = builder
        .overrideProvider(HARNESS_REGISTRY)
        .useValue(registryWith(overrides.registryAdapters));
    }
    if (overrides.conversationService !== undefined) {
      builder = builder
        .overrideProvider(HARNESS_CONVERSATION_SERVICE)
        .useValue(overrides.conversationService);
    }
    return builder.compile();
  }

  // Capture an init rejection without letting a resolved init masquerade as success.
  const initError = (moduleRef: TestingModule): Promise<unknown> =>
    moduleRef.init().then(
      () => new Error('module init resolved but the contract requires it to reject'),
      (err: unknown) => err,
    );

  it('legacy mode: the actual router resolves the embedded runtime, the actual registry is empty, and the conversation-service token is the unavailable sentinel', async () => {
    const moduleRef = await bootChatModule('legacy');
    try {
      await moduleRef.init();
      const router = moduleRef.get(ChatRuntimeRouter, { strict: false });
      expect(router.active.kind).toBe('embedded');

      const registry = moduleRef.get<HarnessRegistry>(HARNESS_REGISTRY, { strict: false });
      expect(registry).toBeInstanceOf(HarnessRegistry);
      expect(registry.list()).toHaveLength(0);

      const service = moduleRef.get<HarnessConversationServiceBinding>(
        HARNESS_CONVERSATION_SERVICE,
        {
          strict: false,
        },
      );
      expect(service).toBe(HARNESS_CONVERSATION_SERVICE_UNAVAILABLE);
    } finally {
      await moduleRef.close();
    }
  });

  it('pi-rpc mode over the REAL empty registry fails closed at init with the typed adapter-unavailable error', async () => {
    const moduleRef = await bootChatModule('pi-rpc');
    try {
      const err = await initError(moduleRef);
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    } finally {
      await closeIgnoringFailedInit(moduleRef);
    }
  });

  it('pi-rpc mode with a pi adapter present but the sentinel conversation service fails closed with the typed conversation-service-unavailable error', async () => {
    const moduleRef = await bootChatModule('pi-rpc', {
      registryAdapters: ['pi'],
      conversationService: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE,
    });
    try {
      const err = await initError(moduleRef);
      expect(err).toBeInstanceOf(ChatRuntimeUnavailableError);
      expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable');
      expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported');
    } finally {
      await closeIgnoringFailedInit(moduleRef);
    }
  });

  it('pi-rpc mode with a pi adapter and a bound conversation service: the actual router selects the harness runtime', async () => {
    const moduleRef = await bootChatModule('pi-rpc', {
      registryAdapters: ['pi'],
      conversationService: boundConversationService,
    });
    try {
      await moduleRef.init();
      const router = moduleRef.get(ChatRuntimeRouter, { strict: false });
      expect(router.active.kind).toBe('harness');
    } finally {
      await moduleRef.close();
    }
  });
});

/**
 * Task Five, Step Two — group 2 (WHOLE production `AppModule` boot, legacy end-to-end wiring).
 *
 * The groups above bound away the heavy cycle to isolate the router. This group instead boots the
 * ACTUAL production `AppModule` (the exact graph `main.ts` runs) in the default LEGACY chat-runtime
 * mode, overriding ONLY the storage/network side-effect adapters so the boot is bounded and offline
 * — never the chat/router/harness/reload/commands surface under test. The bounded fakes are exactly
 * the disk/network leaves:
 *   - `ProviderService` (the #1 hang risk: its real `onModuleInit` starts an unref'd health-check
 *     `setInterval` and fetches Ollama over HTTP) → inert no-op instance;
 *   - `DB_HANDLE`/`DB` → a fake Drizzle-shaped handle that satisfies `runPgliteMigrations` (the local
 *     tier's `DatabaseModule.onModuleInit`) AND `DefaultRoutingRulesSeed.onModuleInit` (which reads a
 *     system-rule count — the fake reports rules already present so the seed insert is skipped),
 *     opening no real database;
 *   - `STORAGE_ADAPTER`/`MEMORY`/`MEMORY_ADAPTER`/`AUTH`/`BRAIN`/`LOG_SERVICE` → inert fakes so no
 *     storage/auth/log backend is contacted.
 * Local tier (the repo's `mosaic.config.json`) already disables BullMQ/Redis and the queue handles;
 * Discord/Telegram/MCP plugins are env-gated and disarmed by deleting their tokens. Nothing about the
 * router, `ChatModule`, `HarnessModule`, or `ChatGateway` is faked — those come from the REAL graph.
 *
 * The boot+init MUST SUCCEED cleanly (proven by `beforeAll` completing and the ChatGateway test
 * passing). Red-first: on this branch `ChatRuntimeRouter` is registered in NO module (ChatModule
 * provides only ChatGateway), so `moduleRef.get(ChatRuntimeRouter)` throws `UnknownElementException`
 * — a WIRING gap, NOT an init failure. That single retrieval is the intended behavioural red; it
 * flips green once Step Three registers the exclusive router. The ChatGateway retrieval and its
 * browser-facing method surface are asserted alongside and pass today, pinning that the boot itself
 * is healthy so the router failure cannot be mistaken for a mis-shaped fake or an unbounded side
 * effect.
 */
describe('AppModule production boot — legacy ChatRuntimeRouter wiring (Task Five, Step Two group 2)', () => {
  // A Drizzle-shaped fake that satisfies both DB consumers reached during a local-tier init:
  //   • runPgliteMigrations(): reads handle.db.$client.exec + handle.db.execute(SELECT hashes);
  //     exec is a no-op and execute yields an empty ledger, so migration statements no-op through.
  //   • DefaultRoutingRulesSeed.seedDefaultRules(): db.select().from().where() must resolve to a
  //     row set — we report a non-zero system-rule count so the seeding INSERT branch is skipped.
  const fakeDb = {
    $client: { exec: async (): Promise<void> => {} },
    execute: async (): Promise<{ rows: unknown[] }> => ({ rows: [] }),
    select: () => ({
      from: () => ({
        where: async (): Promise<Array<{ count: number }>> => [{ count: 1 }],
      }),
    }),
    insert: () => ({ values: async (): Promise<void> => {} }),
  };
  const fakeDbHandle = { db: fakeDb, close: async (): Promise<void> => {} };
  const fakeStorageAdapter = {
    name: 'fake',
    migrate: async (): Promise<void> => {},
    close: async (): Promise<void> => {},
  };
  // Inert stand-in for the real ProviderService: no health-check interval, no Ollama fetch.
  const fakeProviderService = {
    onModuleInit: async (): Promise<void> => {},
    onModuleDestroy: (): void => {},
    getRegistry: () => ({
      getAvailable: () => [],
      getAll: () => [],
      find: () => undefined,
    }),
    getDefaultModel: () => undefined,
    listAvailableModels: () => [],
    listProviders: () => [],
    getAdapter: () => undefined,
    getProvidersHealth: () => [],
  };
  const fakeBrain = { conversations: {}, agents: {} };

  const BOOT_TIMEOUT_MS = 120_000;

  let moduleRef: TestingModule;
  let envSnapshot: Record<string, string | undefined>;

  beforeAll(async () => {
    envSnapshot = { ...process.env };
    // Env hygiene: disarm the network-facing plugins/adapters and pin the legacy runtime mode.
    delete process.env['DATABASE_URL'];
    delete process.env['DISCORD_BOT_TOKEN'];
    delete process.env['TELEGRAM_BOT_TOKEN'];
    delete process.env['MCP_SERVERS'];
    delete process.env['CHAT_HARNESS_RUNTIME']; // resolveChatRuntimeMode → 'legacy'
    process.env['MOSAIC_STORAGE_TIER'] = 'local';

    moduleRef = await Test.createTestingModule({ imports: [AppModule] })
      // Storage/network side-effect adapters ONLY — never the router/chat/harness surface under test.
      .overrideProvider('DB_HANDLE')
      .useValue(fakeDbHandle)
      .overrideProvider('DB')
      .useValue(fakeDb)
      .overrideProvider('STORAGE_ADAPTER')
      .useValue(fakeStorageAdapter)
      .overrideProvider('AUTH')
      .useValue({})
      .overrideProvider('BRAIN')
      .useValue(fakeBrain)
      .overrideProvider('LOG_SERVICE')
      .useValue({})
      .overrideProvider('MEMORY')
      .useValue({})
      .overrideProvider('MEMORY_ADAPTER')
      .useValue({})
      .overrideProvider(ProviderService)
      .useValue(fakeProviderService)
      .compile();

    // The boot itself MUST succeed cleanly — a rejection here is a bounding failure, not the red.
    await moduleRef.init();
  }, BOOT_TIMEOUT_MS);

  afterAll(async () => {
    if (moduleRef) await moduleRef.close();
    for (const key of Object.keys(process.env)) {
      if (!(key in envSnapshot)) delete process.env[key];
    }
    for (const [key, value] of Object.entries(envSnapshot)) {
      if (value === undefined) delete process.env[key];
      else process.env[key] = value;
    }
  });

  // Passes TODAY: the real ChatGateway is provided by the real ChatModule and its browser-facing
  // surface exists. This pins that the whole-AppModule boot came up healthy, so the router failure
  // below is unambiguously a wiring gap and not a mis-shaped fake or an unbounded side effect.
  it('boots the whole AppModule and exposes the real ChatGateway with its browser-facing methods', () => {
    const gateway = moduleRef.get(ChatGateway, { strict: false });
    expect(typeof gateway.broadcastReload).toBe('function');
    expect(typeof gateway.getModelOverride).toBe('function');
    expect(typeof gateway.setModelOverride).toBe('function');
    expect(typeof gateway.broadcastSessionInfo).toBe('function');
  });

  // RED TODAY: ChatRuntimeRouter is registered in no module on this branch, so this retrieval throws
  // UnknownElementException — the intended red-first wiring failure. GREEN once Step Three registers
  // the exclusive router in the production graph, where legacy mode resolves the embedded runtime.
  it('resolves the exclusive ChatRuntimeRouter to the embedded runtime in legacy mode', () => {
    const router = moduleRef.get(ChatRuntimeRouter, { strict: false });
    expect(router.active.kind).toBe('embedded');
  });
});
