import type {
  AIProvider,
  AIProviderRegistry,
  ChatMessage,
  ChatRequestOptions,
} from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import type { AgentConfig } from '../../config/config';
import type { ChatDriver } from '../chat-driver/chat-driver';
// 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-1508 — `recordExternalCost` is OPTIONAL on `AiDriver`, so a missing delegation here
// is not a compile error: the host's `driver?.recordExternalCost?.(…)` simply evaluates to
// undefined and every post-resolve charge from every widget disappears — silently, and only
// in orchestrated setups. That is the same invisible-when-broken failure the method exists to
// fix, so the delegation gets a test rather than trust.

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

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' })], {
    sessionKey: 'k',
  });

/** The orchestrator does not expose `requestInteraction`, so the interaction is ARRANGED on the
 *  wrapped driver. Every assertion below goes through the orchestrator's own public surface. */
const wrapped = (driver: OrchestratingDriver): ChatDriver =>
  (driver as unknown as { chatDriver: ChatDriver }).chatDriver;

const Suite = createLogicSuite('orchestrating-driver external cost');

Suite('post-resolve spend delegates to the wrapped driver', async () => {
  const driver = makeDriver();
  const inner = wrapped(driver);
  const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
  const id = driver.getRawHistory().at(-1)!.interaction!.interactionId;
  driver.resolveInteraction(id, { status: 'approved', costUsd: 0.5 });
  await pending;

  assert.is(driver.recordExternalCost(id, 0.25), true);

  const msg = driver.getRawHistory().find((m) => m.interaction?.interactionId === id);
  assert.is(msg?.externalCostUsd, 0.75);
});

Suite('an unknown interaction is reported as unrecorded, not thrown', async () => {
  const driver = makeDriver();
  assert.is(driver.recordExternalCost('never-existed', 0.25), false);
});

Suite('the announcement reaches the HOST, which only ever holds this wrapper', async () => {
  // The delegation test above passes with the forward missing: the money still lands on the
  // message. What breaks is quieter — `createDriver` hands the host an OrchestratingDriver for any
  // non-empty `agents`, so an event the inner driver raises and the constructor's forward list
  // omits is one the host never sees. The totals then never refresh and the save is never
  // scheduled, and because this flow's interaction is the last thing in the session, "stale until
  // the next history change" means permanently. Asserted on the WRAPPER for that reason.
  const driver = makeDriver();
  const inner = wrapped(driver);
  const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
  const id = driver.getRawHistory().at(-1)!.interaction!.interactionId;
  driver.resolveInteraction(id, { status: 'approved' });
  await pending;

  let seen = 0;
  let detail: { interactionId?: string; costUsd?: number } | undefined;
  driver.addEventListener('external-cost-recorded', (e) => {
    seen += 1;
    detail = (e as CustomEvent).detail;
  });
  // …and never on `history-updated`, which would rebuild the reporting widget.
  let historyUpdated = 0;
  driver.addEventListener('history-updated', () => {
    historyUpdated += 1;
  });

  driver.recordExternalCost(id, 0.25);

  assert.is(seen, 1);
  assert.is(detail?.costUsd, 0.25);
  assert.is(detail?.interactionId, id);
  assert.is(historyUpdated, 0);
});

Suite.run();
