import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createAgentDelegateTool } from './agent-delegate.js';
import type { AgentDelegateDeps } from './agent-delegate.js';
import type { AgentConfigRegistry } from '../../agents/registry.js';

function createMockRegistry(configs: Record<string, { systemPrompt?: string; modelTier?: string }>): AgentConfigRegistry {
  const entries = Object.entries(configs).map(([name, cfg]) => ({
    name,
    systemPrompt: cfg.systemPrompt,
    modelTier: cfg.modelTier as 'fast' | 'default' | 'complex' | undefined,
  }));

  return {
    get: (name: string) => entries.find(e => e.name === name),
    list: () => entries,
  } as unknown as AgentConfigRegistry;
}

function createMockOrchestrator(
  response?: { content: string; usage: { inputTokens: number; outputTokens: number }; tier: 'fast' | 'default' | 'complex' | 'local' },
): AgentDelegateDeps['orchestrator'] {
  return {
    delegate: vi.fn().mockResolvedValue(response ?? {
      content: 'Mock agent response',
      usage: { inputTokens: 100, outputTokens: 50 },
      tier: 'default',
    }),
  };
}

describe('agent.delegate tool', () => {
  let deps: AgentDelegateDeps;
  let mockOrchestrator: AgentDelegateDeps['orchestrator'];

  beforeEach(() => {
    mockOrchestrator = createMockOrchestrator();
    deps = {
      registry: createMockRegistry({
        research: { systemPrompt: 'You are a research agent.', modelTier: 'default' },
        code: { systemPrompt: 'You are a code agent.', modelTier: 'complex' },
        comms: { modelTier: 'fast' },
      }),
      orchestrator: mockOrchestrator,
    };
  });

  it('creates a tool with correct name and schema', () => {
    const tool = createAgentDelegateTool(deps);
    expect(tool.name).toBe('agent.delegate');
    expect(tool.inputSchema.required).toContain('agent');
    expect(tool.inputSchema.required).toContain('task');
    expect(tool.inputSchema.properties).toHaveProperty('agent');
    expect(tool.inputSchema.properties).toHaveProperty('task');
    expect(tool.inputSchema.properties).toHaveProperty('max_tokens');
  });

  it('delegates to the correct agent with configured tier and prompt', async () => {
    const tool = createAgentDelegateTool(deps);
    const result = await tool.execute({ agent: 'research', task: 'Find info about TypeScript 6' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Agent: research');
    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith({
      tier: 'default',
      systemPrompt: 'You are a research agent.',
      message: 'Find info about TypeScript 6',
      maxTokens: 4096,
    });
  });

  it('uses complex tier for code agent', async () => {
    const tool = createAgentDelegateTool(deps);
    await tool.execute({ agent: 'code', task: 'Review this function' });

    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
      expect.objectContaining({ tier: 'complex' }),
    );
  });

  it('uses fast tier for comms agent', async () => {
    const tool = createAgentDelegateTool(deps);
    await tool.execute({ agent: 'comms', task: 'Draft a reply' });

    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
      expect.objectContaining({ tier: 'fast' }),
    );
  });

  it('uses generic system prompt when agent config has none', async () => {
    const tool = createAgentDelegateTool(deps);
    await tool.execute({ agent: 'comms', task: 'Summarize email' });

    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
      expect.objectContaining({
        systemPrompt: expect.stringContaining('sub-agent named "comms"'),
      }),
    );
  });

  it('returns error for unknown agent', async () => {
    const tool = createAgentDelegateTool(deps);
    const result = await tool.execute({ agent: 'nonexistent', task: 'Do something' });

    expect(result.success).toBe(false);
    expect(result.error).toContain('not found');
    expect(result.error).toContain('research');
    expect(result.error).toContain('code');
    expect(result.error).toContain('comms');
  });

  it('respects custom max_tokens', async () => {
    const tool = createAgentDelegateTool(deps);
    await tool.execute({ agent: 'research', task: 'Quick lookup', max_tokens: 1024 });

    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
      expect.objectContaining({ maxTokens: 1024 }),
    );
  });

  it('includes token usage in output', async () => {
    const tool = createAgentDelegateTool(deps);
    const result = await tool.execute({ agent: 'research', task: 'Test query' });

    expect(result.output).toContain('Tokens: 100+50');
    expect(result.output).toContain('Tier: default');
  });

  it('handles orchestrator errors gracefully', async () => {
    const failingOrchestrator = {
      delegate: vi.fn().mockRejectedValue(new Error('Model provider unavailable')),
    } as AgentDelegateDeps['orchestrator'];

    const tool = createAgentDelegateTool({ ...deps, orchestrator: failingOrchestrator });
    const result = await tool.execute({ agent: 'research', task: 'This will fail' });

    expect(result.success).toBe(false);
    expect(result.error).toBe('Model provider unavailable');
  });

  it('falls back to default tier when agent has no tier configured', async () => {
    const registry = createMockRegistry({
      generic: { systemPrompt: 'Generic agent' },
    });
    const tool = createAgentDelegateTool({ registry, orchestrator: mockOrchestrator });
    await tool.execute({ agent: 'generic', task: 'Do something' });

    expect((mockOrchestrator.delegate as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
      expect.objectContaining({ tier: 'default' }),
    );
  });

  it('returns empty available list when no agents configured', async () => {
    const emptyRegistry = createMockRegistry({});
    const tool = createAgentDelegateTool({ registry: emptyRegistry, orchestrator: mockOrchestrator });
    const result = await tool.execute({ agent: 'anything', task: 'Test' });

    expect(result.success).toBe(false);
    expect(result.error).toContain('none');
  });
});
