import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import type { AgentConfig } from '../config/config';
import { stripAgentHandlers } from './strip-agent-handlers';

const cfg = (overrides: Record<string, unknown>): AgentConfig =>
  ({ name: 'agent', ...overrides }) as unknown as AgentConfig;

/** Mirrors what redux's serializable-state check does: find any function anywhere. */
const hasFunctionDeep = (value: unknown): boolean => {
  if (typeof value === 'function') return true;
  if (Array.isArray(value)) return value.some(hasFunctionDeep);
  if (value !== null && typeof value === 'object') {
    return Object.values(value).some(hasFunctionDeep);
  }
  return false;
};

const suite = createLogicSuite('stripAgentHandlers');

suite('drops object-form toolHandlers (the redux-leak regression)', () => {
  const stripped = stripAgentHandlers(
    cfg({ toolHandlers: { vfs_list: async () => 'x', vfs_read: async () => 'y' } }),
  ) as Record<string, unknown>;
  // The whole handler bag is gone — not left behind with live functions inside.
  assert.not.ok(stripped.toolHandlers);
  assert.not.ok(hasFunctionDeep(stripped));
});

suite('drops factory-function toolHandlers and the lifecycle/dispatch hooks', () => {
  const stripped = stripAgentHandlers(
    cfg({
      toolHandlers: () => ({ t: async () => 'x' }),
      onActivate: async () => {},
      onDeactivate: async () => {},
      getDebugSnapshot: () => ({}),
      onUnresolvedTool: () => 'redirect',
    }),
  ) as Record<string, unknown>;
  assert.not.ok(stripped.toolHandlers);
  assert.not.ok(stripped.onActivate);
  assert.not.ok(stripped.onDeactivate);
  assert.not.ok(stripped.getDebugSnapshot);
  assert.not.ok(stripped.onUnresolvedTool);
});

suite('drops function-form resolvers but keeps static forms and plain data', () => {
  const stripped = stripAgentHandlers(
    cfg({
      systemPrompt: () => 'dynamic', // function → dropped
      displayName: 'Static Name', // string → kept
      temperature: 0.5, // number → kept
      toolChoice: 'auto', // string → kept
      toolDefinitions: [{ name: 'x', description: 'd', parameters: {} }], // data array → kept
      manualSelection: { enabled: true, hint: 'pick me' }, // data object → kept
    }),
  ) as Record<string, unknown>;
  assert.not.ok(stripped.systemPrompt);
  assert.is(stripped.displayName, 'Static Name');
  assert.is(stripped.temperature, 0.5);
  assert.is(stripped.toolChoice, 'auto');
  assert.equal(stripped.toolDefinitions, [{ name: 'x', description: 'd', parameters: {} }]);
  assert.equal(stripped.manualSelection, { enabled: true, hint: 'pick me' });
});

suite('recurses into subAgents, stripping their object-form toolHandlers', () => {
  const stripped = stripAgentHandlers(
    cfg({
      name: 'boss',
      toolHandlers: () => ({}),
      subAgents: [
        cfg({
          name: 'worker',
          toolHandlers: { vfs_list: async () => 'x', vfs_read: async () => 'y' },
        }),
      ],
    }),
  ) as Record<string, unknown>;
  const subs = stripped.subAgents as Array<Record<string, unknown>>;
  assert.is(subs.length, 1);
  assert.is(subs[0].name, 'worker');
  assert.not.ok(subs[0].toolHandlers, 'sub-agent toolHandlers must be stripped (the reported bug)');
  // Nothing function-valued survives anywhere in the projection.
  assert.not.ok(hasFunctionDeep(stripped));
});

suite('produces a projection with no function at any depth', () => {
  const stripped = stripAgentHandlers(
    cfg({
      name: 'boss',
      onActivate: async () => {},
      subAgents: [cfg({ name: 'w', toolHandlers: { t: async () => {} } })],
    }),
  );
  assert.not.ok(hasFunctionDeep(stripped));
  // Round-trips to the expected serializable shape.
  assert.equal(JSON.parse(JSON.stringify(stripped)), { name: 'boss', subAgents: [{ name: 'w' }] });
});

suite.run();
