import { EventEmitter } from 'events';
import { SUBAGENT_WORKER_PROTOCOL_VERSION } from '../src/modules/netaclaw/subagent/process_protocol.js';

jest.mock('../src/modules/netaclaw/runtime/model_selection.js', () => ({
  initDefaultProviders: jest.fn(),
}));

jest.mock('../src/modules/netaclaw/runtime/agent.js', () => ({
  runAgent: jest.fn(),
}));

describe('subagent worker', () => {
  let originalStdin: NodeJS.ReadStream;
  let originalStdoutWrite: typeof process.stdout.write;
  let originalExitCode: typeof process.exitCode;
  let stdoutLines: any[];

  beforeEach(() => {
    jest.resetModules();
    stdoutLines = [];
    originalStdin = process.stdin;
    originalStdoutWrite = process.stdout.write;
    originalExitCode = process.exitCode;
    process.exitCode = undefined;
    process.stdout.write = jest.fn((chunk: any) => {
      String(chunk).split(/\r?\n/).filter(Boolean).forEach(line => {
        stdoutLines.push(JSON.parse(line));
      });
      return true;
    }) as any;
  });

  afterEach(() => {
    Object.defineProperty(process, 'stdin', {
      configurable: true,
      value: originalStdin,
    });
    process.stdout.write = originalStdoutWrite;
    process.exitCode = originalExitCode;
  });

  const installStdin = (payload: unknown) => {
    const stdin = new EventEmitter() as NodeJS.ReadStream;
    stdin.setEncoding = jest.fn() as any;
    Object.defineProperty(process, 'stdin', {
      configurable: true,
      value: stdin,
    });
    setImmediate(() => {
      stdin.emit('data', `${JSON.stringify(payload)}\n`);
      stdin.emit('end');
    });
  };

  const importWorker = async () => {
    jest.isolateModules(() => {
      require('../src/modules/netaclaw/subagent/worker.js');
    });
    await new Promise(resolve => setImmediate(resolve));
    await new Promise(resolve => setImmediate(resolve));
  };

  const baseEnvelope = {
    protocolVersion: SUBAGENT_WORKER_PROTOCOL_VERSION,
    runId: 'run-worker-1',
    sessionId: 'session-1',
    task: {
      goal: 'Summarize architecture.',
    },
    agentConfig: {
      name: 'worker-agent',
      systemPrompt: 'Use concise output.',
      model: 'openai:gpt-5.4',
      apiKey: 'test-key',
    },
    toolNames: [],
    toolManifest: [],
  };

  it('executes a no-tool agent run and emits run_end', async () => {
    const { runAgent } = require('../src/modules/netaclaw/runtime/agent.js');
    runAgent.mockImplementation(async (params: any) => {
      params.onToken('partial');
      return {
        finalContent: 'worker final',
        usage: { inputTokens: 5, outputTokens: 6 },
        toolCallCount: 0,
        messages: [{ role: 'assistant', content: 'worker final' }],
      };
    });
    installStdin(baseEnvelope);

    await importWorker();

    expect(runAgent).toHaveBeenCalledWith(
      expect.objectContaining({
        tools: [],
        toolNames: [],
        userMessage: 'Summarize architecture.',
        history: [],
        agentConfig: expect.objectContaining({
          name: 'worker-agent',
        }),
      })
    );
    expect(stdoutLines.map(line => line.type)).toEqual(['run_start', 'token', 'run_end']);
    expect(stdoutLines[2].result).toEqual({
      finalContent: 'worker final',
      usage: { inputTokens: 5, outputTokens: 6 },
      toolCallCount: 0,
      metadata: { messageCount: 1, proxiedToolNames: [] },
    });
  });

  it('passes supported worker-local tools into runAgent', async () => {
    const { runAgent } = require('../src/modules/netaclaw/runtime/agent.js');
    runAgent.mockResolvedValue({
      finalContent: 'tool-backed final',
      usage: { inputTokens: 2, outputTokens: 3 },
      toolCallCount: 1,
      messages: [{ role: 'assistant', content: 'tool-backed final' }],
    });
    installStdin({
      ...baseEnvelope,
      toolManifest: [
        { name: 'bash', visibility: 'tool', capability: 'text', kind: 'builtin', executionMode: 'parallel', supportedInWorker: true, requiresShell: true },
        { name: 'read_file', visibility: 'tool', capability: 'text', kind: 'builtin', executionMode: 'parallel', supportedInWorker: true },
        { name: 'list_dir', visibility: 'tool', capability: 'text', kind: 'builtin', executionMode: 'parallel', supportedInWorker: true },
      ],
    });

    await importWorker();

    expect(runAgent).toHaveBeenCalledWith(
      expect.objectContaining({
        toolNames: ['bash', 'read_file', 'list_dir'],
        tools: [
          expect.objectContaining({ name: 'bash' }),
          expect.objectContaining({ name: 'read_file' }),
          expect.objectContaining({ name: 'list_dir' }),
        ],
      })
    );
    expect(stdoutLines.map(line => line.type)).toEqual(['run_start', 'run_end']);
    expect(stdoutLines[1].result.finalContent).toBe('tool-backed final');
  });

  it('rejects unsupported tool execution until worker registry wiring exists', async () => {
    installStdin({
      ...baseEnvelope,
      toolManifest: [
        { name: 'write_file', visibility: 'tool', capability: 'text', kind: 'builtin', executionMode: 'sequential', supportedInWorker: false, requiresWrite: true },
        { name: 'delegate_task', visibility: 'tool', capability: 'text', kind: 'delegation', executionMode: 'sequential', supportedInWorker: false },
      ],
    });

    await importWorker();

    expect(stdoutLines.map(line => line.type)).toEqual(['run_start', 'run_error']);
    expect(stdoutLines[1].error).toBe(
      'Subagent subprocess worker does not support tools: write_file, delegate_task'
    );
    expect(process.exitCode).toBe(1);
  });

  it('falls back to toolNames when manifest is absent for backward compatibility', async () => {
    const { runAgent } = require('../src/modules/netaclaw/runtime/agent.js');
    runAgent.mockResolvedValue({
      finalContent: 'legacy fallback final',
      usage: { inputTokens: 1, outputTokens: 1 },
      toolCallCount: 0,
      messages: [{ role: 'assistant', content: 'legacy fallback final' }],
    });
    installStdin({
      ...baseEnvelope,
      toolManifest: undefined,
      toolNames: ['read_file'],
      policy: {
        workspaceRoots: ['C:\\repo'],
      },
    });

    await importWorker();

    expect(runAgent).toHaveBeenCalledWith(
      expect.objectContaining({
        toolNames: ['read_file'],
        tools: [expect.objectContaining({ name: 'read_file' })],
      })
    );
    expect(stdoutLines.map(line => line.type)).toEqual(['run_start', 'run_end']);
  });

  it('uses manifest routes as the canonical toolNames passed into runAgent', async () => {
    const { runAgent } = require('../src/modules/netaclaw/runtime/agent.js');
    runAgent.mockResolvedValue({
      finalContent: 'manifest canonical final',
      usage: { inputTokens: 1, outputTokens: 2 },
      toolCallCount: 0,
      messages: [{ role: 'assistant', content: 'manifest canonical final' }],
    });
    installStdin({
      ...baseEnvelope,
      toolNames: ['read_file', 'write_file'],
      toolManifest: [
        {
          name: 'read_file',
          visibility: 'tool',
          capability: 'text',
          kind: 'builtin',
          executionMode: 'parallel',
          supportedInWorker: true,
          workerRoutingHint: 'local',
        },
        {
          name: 'write_file',
          visibility: 'tool',
          capability: 'text',
          kind: 'builtin',
          executionMode: 'sequential',
          supportedInWorker: false,
          workerRoutingHint: 'main-process-proxy',
          requiresWrite: true,
        },
      ],
    });

    await importWorker();

    expect(runAgent).toHaveBeenCalledWith(
      expect.objectContaining({
        toolNames: ['read_file', 'write_file'],
        tools: [
          expect.objectContaining({ name: 'read_file' }),
          expect.objectContaining({ name: 'write_file' }),
        ],
      })
    );
    expect(stdoutLines.map(line => line.type)).toEqual(['run_start', 'log', 'run_end']);
  });
});
