import type {
  AIProvider,
  AIProviderRegistry,
  ChatMessage,
  ChatRequestOptions,
} from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import type { AgentConfig } from '../../config/config';
// Side-effect import — MUST precede `./orchestrating-driver` so the driver
// subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
import '../chat-driver/align-event-globals';
import { OrchestratingDriver } from './orchestrating-driver';

// GENC-1351 — a set routing pin must be honoured on the next turn. This is the
// invariant that broke on restore (found via the §5.8 diagnostics log): the pin
// was hydrated into the store but never pushed to the driver, so the first
// post-restore message fell through to the classifier and re-routed away from the
// pinned agent. `Guided Booking` here is `excludeFromClassifier`, so the classifier
// can NEVER pick it — a turn landing on it proves the pin (not classification)
// routed it. The element-side fix (restore → `driver.setPinnedAgent`) is what makes
// the pin reach the driver; this locks the driver half of the contract.

const makeRegistry = (provider: AIProvider): AIProviderRegistry => ({
  get: () => provider,
  default: () => provider,
  defaultName: () => 'test',
  names: () => ['test'],
  getStatus: async () => null,
  listStatuses: async () => [],
});

/** A provider that ends every turn immediately with a plain-text reply (no tools). */
const noToolProvider = (): AIProvider =>
  ({
    chat: async (
      _history: ChatMessage[],
      _userMessage: string,
      _options?: ChatRequestOptions,
    ): Promise<ChatMessage> => ({ role: 'assistant', content: 'ok' }),
  }) as AIProvider;

const agent = (overrides: Partial<AgentConfig> & { name: string }): AgentConfig =>
  ({ description: 'test agent', ...overrides }) as AgentConfig;

const makeDriver = () =>
  new OrchestratingDriver(
    makeRegistry(noToolProvider()),
    [
      agent({ name: 'Genesis Assistant' }), // classifier-eligible specialist
      agent({ name: 'Guided Booking', excludeFromClassifier: true }), // pin-only
    ],
    { sessionKey: 'k' },
  );

const Suite = createLogicSuite('orchestrating-driver-pin');

Suite('a set pin routes the next turn to the pinned agent (bypassing the classifier)', async () => {
  const driver = makeDriver();
  driver.setPinnedAgent('Guided Booking');
  await driver.sendMessage('hi');
  assert.is(
    driver.getActiveAgent()?.name,
    'Guided Booking',
    'pinned agent ran; it is excludeFromClassifier, so only the pin could have routed it',
  );
});

Suite('the pin is sticky across turns', async () => {
  const driver = makeDriver();
  driver.setPinnedAgent('Guided Booking');
  await driver.sendMessage('one');
  await driver.sendMessage('two');
  assert.is(driver.getActiveAgent()?.name, 'Guided Booking', 'still pinned on the second turn');
});

// GENC-1351 — the flow-owner lock is the *unpinned* routing path. A classifier-routed
// stateful flow (flow-owned but never user-pinned) must resume on restore without
// re-classifying: the persister re-applies BOTH `setPinnedAgent` and `setFlowOwner` on
// restore, and `resolvePinnedAgent` reads `flowOwnerAgentName ?? pinnedAgentName`. Here
// `Guided Booking` is `excludeFromClassifier` AND left unpinned, so a turn landing on it
// proves the flow-owner lock (not the pin, not classification) routed it.
Suite(
  'a set flow-owner lock routes the next turn to that agent (bypassing the classifier)',
  async () => {
    const driver = makeDriver();
    driver.setFlowOwner('Guided Booking'); // flow-owned but NOT pinned
    await driver.sendMessage('hi');
    assert.is(
      driver.getActiveAgent()?.name,
      'Guided Booking',
      'flow-owner routed it; it is excludeFromClassifier and unpinned, so only the flow-owner lock could have',
    );
  },
);

Suite.run();
