import { describe, it, expect } from 'vitest';
import { parseAgentsMd, matchToolGlob } from '../agents.js';
import { toolJsonSchemas } from '../tools.js';

describe('agent tool allowlist', () => {
  const plannerMd = `# Agents

## Planner
---
temperature: 0.6
tools: [view_file, grep, list_dir, find_files]
description: Read-only planner
---
You plan.
`;

  it('parses an agent with a restricted tool allowlist', () => {
    const { agents, errors } = parseAgentsMd(plannerMd);
    expect(errors).toHaveLength(0);
    expect(agents).toHaveLength(1);
    const planner = agents[0]!;
    expect(planner.name).toBe('Planner');
    expect(planner.tools).toEqual(['view_file', 'grep', 'list_dir', 'find_files']);
  });

  it('stream-phase filter: agent allowlist excludes tools not in the list', () => {
    const { agents } = parseAgentsMd(plannerMd);
    const planner = agents[0]!;
    const allSchemas = toolJsonSchemas();
    const filtered = allSchemas.filter((t) =>
      matchToolGlob(t.function.name, planner.tools),
    );
    const filteredNames = filtered.map((t) => t.function.name);
    expect(filteredNames).toContain('view_file');
    expect(filteredNames).toContain('grep');
    expect(filteredNames).not.toContain('edit_file');
    expect(filteredNames).not.toContain('web_search');
    expect(filteredNames).not.toContain('get_codebase_overview');
    expect(filtered).toHaveLength(4);
  });

  it('tool-phase guard: rejects tool call not in agent allowlist', () => {
    const { agents } = parseAgentsMd(plannerMd);
    const planner = agents[0]!;
    expect(matchToolGlob('edit_file', planner.tools)).toBe(false);
    expect(matchToolGlob('create_file', planner.tools)).toBe(false);
    expect(matchToolGlob('delete_file', planner.tools)).toBe(false);
    expect(matchToolGlob('web_search', planner.tools)).toBe(false);
  });

  it('tool-phase guard: allows tool call in agent allowlist', () => {
    const { agents } = parseAgentsMd(plannerMd);
    const planner = agents[0]!;
    expect(matchToolGlob('view_file', planner.tools)).toBe(true);
    expect(matchToolGlob('grep', planner.tools)).toBe(true);
    expect(matchToolGlob('list_dir', planner.tools)).toBe(true);
    expect(matchToolGlob('find_files', planner.tools)).toBe(true);
  });

  it('null/absent tools field defaults to all tools (no regression)', () => {
    const noToolsMd = `# Agents

## Default
---
temperature: 0.7
description: Uses all tools
---
Default agent.
`;
    const { agents } = parseAgentsMd(noToolsMd);
    const agent = agents[0]!;
    const allSchemas = toolJsonSchemas();
    const filtered = allSchemas.filter((t) =>
      matchToolGlob(t.function.name, agent.tools),
    );
    expect(filtered.length).toBe(allSchemas.length);
  });

  it('builder agent: write tools filtered out when not in ALL_TOOLS (BooChat context)', () => {
    const builderMd = `# Agents

## Builder
---
temperature: 0.6
tools: [view_file, grep, list_dir, find_files, edit_file, create_file, delete_file, apply_pending, rewind]
description: Read and write tools
---
You build.
`;
    const { agents } = parseAgentsMd(builderMd);
    const builder = agents[0]!;
    expect(matchToolGlob('view_file', builder.tools)).toBe(true);
    expect(matchToolGlob('grep', builder.tools)).toBe(true);
    // Write tools not in server's ALL_TOOLS are silently filtered during parsing.
    // In BooCoder context (where ALL_TOOLS includes write tools), they'd be retained.
    expect(builder.tools).not.toContain('edit_file');
    expect(builder.tools).not.toContain('create_file');
    expect(matchToolGlob('web_search', builder.tools)).toBe(false);
  });

  it('matchToolGlob rejects hallucinated tool against exact allowlist', () => {
    const allowlist = ['view_file', 'grep', 'list_dir'];
    expect(matchToolGlob('edit_file', allowlist)).toBe(false);
    expect(matchToolGlob('rm_rf', allowlist)).toBe(false);
    expect(matchToolGlob('view_file_extended', allowlist)).toBe(false);
  });
});
