import { describe, it, expect } from 'vitest';
import type { ToolCall } from '../../llm/openai-compat.js';
import { ToolResultCache } from '../context/tool-result-cache.js';
import { routeToolThroughCache } from './tool-cache-routing.js';

const call = (name: string, args: Record<string, unknown>): ToolCall => ({
  id: `tc-${name}`,
  type: 'function',
  function: { name, arguments: JSON.stringify(args) },
});

const WS = '/ws';

describe('routeToolThroughCache', () => {
  it('returns null when no cache is provided', () => {
    expect(routeToolThroughCache(call('Read', { file_path: 'a.txt' }), undefined, WS)).toBeNull();
  });

  it('returns null for an unknown tool', () => {
    const cache = new ToolResultCache();
    expect(routeToolThroughCache(call('Bash', { command: 'ls' }), cache, WS)).toBeNull();
  });

  it('returns null when arguments are not valid JSON', () => {
    const cache = new ToolResultCache();
    const bad: ToolCall = { id: 'x', type: 'function', function: { name: 'Read', arguments: 'not json' } };
    expect(routeToolThroughCache(bad, cache, WS)).toBeNull();
  });

  it('routes Read to a file-volatility entry with a stable cache key and miss', () => {
    const cache = new ToolResultCache();
    const route = routeToolThroughCache(call('Read', { file_path: 'a.txt' }), cache, WS);
    expect(route).not.toBeNull();
    expect(route!.volatility).toBe('file');
    expect(route!.displayLabel).toBe('Read a.txt');
    expect(route!.touchedPaths).toEqual(['a.txt']);
    expect(route!.hit).toBeNull();
  });

  it('returns a cache hit for Read when an entry already exists at the key', () => {
    const cache = new ToolResultCache();
    const key = routeToolThroughCache(call('Read', { file_path: 'a.txt' }), cache, WS)!.cacheKey;
    cache.set({
      key,
      toolName: 'Read',
      resultText: 'cached body',
      createdAt: new Date().toISOString(),
      sourceMovement: 'investigate',
      touchedPaths: ['a.txt'],
      volatility: 'file',
    });
    const route = routeToolThroughCache(call('Read', { file_path: 'a.txt' }), cache, WS);
    expect(route!.hit).not.toBeNull();
    expect(route!.hit!.resultText).toBe('cached body');
  });

  it('routes Grep to a search-volatility entry', () => {
    const cache = new ToolResultCache();
    const route = routeToolThroughCache(call('Grep', { pattern: 'foo', path: 'src' }), cache, WS);
    expect(route!.volatility).toBe('search');
    expect(route!.displayLabel).toBe('Grep foo in src');
    expect(route!.touchedPaths).toEqual(['src']);
  });

  it('routes Glob to a search-volatility entry', () => {
    const cache = new ToolResultCache();
    const route = routeToolThroughCache(call('Glob', { pattern: '**/*.ts' }), cache, WS);
    expect(route!.volatility).toBe('search');
    expect(route!.displayLabel).toBe('Glob **/*.ts');
    expect(route!.touchedPaths).toEqual([]);
  });

  it('routes WebFetch to a url-volatility entry with no touched paths', () => {
    const cache = new ToolResultCache();
    const route = routeToolThroughCache(call('WebFetch', { url: 'https://example.com' }), cache, WS);
    expect(route!.volatility).toBe('url');
    expect(route!.displayLabel).toBe('WebFetch https://example.com');
    expect(route!.touchedPaths).toEqual([]);
  });

  it('routes a Read of an office extension (.xlsx) to a file-volatility entry', () => {
    const cache = new ToolResultCache();
    const route = routeToolThroughCache(call('Read', { file_path: 'doc.xlsx', sheet: 'Q1' }), cache, WS);
    expect(route!.volatility).toBe('file');
    expect(route!.displayLabel).toBe('Read doc.xlsx');
    expect(route!.touchedPaths).toEqual(['doc.xlsx']);
  });

  it('returns null for Read without a file_path', () => {
    const cache = new ToolResultCache();
    expect(routeToolThroughCache(call('Read', {}), cache, WS)).toBeNull();
  });
});
