import { describe, it, expect, vi } from 'vitest';
import { executeTool } from './tool-request.js';
import type { ToolContext } from './core.js';

function ctx(overrides: Partial<ToolContext> = {}): ToolContext {
  return { workspacePath: '/tmp/x', editAllowed: false, ...overrides } as ToolContext;
}

describe('RequestTool', () => {
  it('returns null for non-RequestTool names', async () => {
    expect(await executeTool('Read', {}, ctx())).toBeNull();
  });

  it('requires name and reason', async () => {
    expect((await executeTool('RequestTool', { reason: 'x' }, ctx()))?.isError).toBe(true);
    expect((await executeTool('RequestTool', { name: 'Bash' }, ctx()))?.isError).toBe(true);
  });

  it('already-available tool → not recorded, told to just call it', async () => {
    const rec = vi.fn();
    const r = await executeTool(
      'RequestTool',
      { name: 'Bash', reason: 'r' },
      ctx({ effectiveToolNames: ['Bash'], recordToolRequest: rec }),
    );
    expect(r?.isError).toBe(false);
    expect(r?.output).toContain('既に利用可能');
    expect(rec).not.toHaveBeenCalled();
  });

  it('exists in catalog but not allowed here → recorded as requested', async () => {
    const rec = vi.fn();
    await executeTool(
      'RequestTool',
      { name: 'WebSearch', reason: 'need search' },
      ctx({ effectiveToolNames: ['Read'], knownToolNames: ['Read', 'WebSearch'], recordToolRequest: rec }),
    );
    expect(rec).toHaveBeenCalledWith(
      expect.objectContaining({ toolName: 'WebSearch', category: 'requested', reason: 'need search' }),
    );
  });

  it('mcp__ tool not in catalog → still recorded as requested (cannot enumerate MCP)', async () => {
    const rec = vi.fn();
    await executeTool(
      'RequestTool',
      { name: 'mcp__foo__bar', reason: 'r' },
      ctx({ effectiveToolNames: [], knownToolNames: [], recordToolRequest: rec }),
    );
    expect(rec).toHaveBeenCalledWith(expect.objectContaining({ category: 'requested' }));
  });

  it('not in catalog → ERROR (existence enforced), still recorded as unknown', async () => {
    const rec = vi.fn();
    const r = await executeTool(
      'RequestTool',
      { name: 'NotARealTool', reason: 'r' },
      ctx({ effectiveToolNames: [], knownToolNames: ['Read'], recordToolRequest: rec }),
    );
    expect(rec).toHaveBeenCalledWith(expect.objectContaining({ toolName: 'NotARealTool', category: 'unknown' }));
    expect(r?.output).toContain('存在しません');
    // A non-existent tool is a hard error so the agent does not mistake it for a
    // pending approval / successful call.
    expect(r?.isError).toBe(true);
    expect(r?.output).not.toContain('記録しました');
  });

  it('interactive run + not-allowed → records pending and sets the pause flag', async () => {
    const rec = vi.fn();
    const c = ctx({ effectiveToolNames: ['Read'], knownToolNames: ['Read', 'WebSearch'], recordToolRequest: rec, toolApprovalInteractive: true });
    const r = await executeTool('RequestTool', { name: 'WebSearch', reason: 'need it' }, c);
    expect(rec).toHaveBeenCalledWith(expect.objectContaining({ toolName: 'WebSearch', category: 'requested', status: 'pending' }));
    expect(c.pendingToolApproval).toEqual({ toolName: 'WebSearch' });
    expect(r?.output).toContain('承認');
  });

  it('non-interactive run + not-allowed → auto_denied, no pause flag', async () => {
    const rec = vi.fn();
    const c = ctx({ effectiveToolNames: ['Read'], knownToolNames: ['Read', 'WebSearch'], recordToolRequest: rec });
    await executeTool('RequestTool', { name: 'WebSearch', reason: 'r' }, c);
    expect(rec).toHaveBeenCalledWith(expect.objectContaining({ status: 'auto_denied' }));
    expect(c.pendingToolApproval).toBeFalsy();
  });

  it('never throws when no recorder is wired', async () => {
    const r = await executeTool('RequestTool', { name: 'WebSearch', reason: 'r' }, ctx({ knownToolNames: ['WebSearch'] }));
    expect(r?.isError).toBe(false);
  });
});
