import { describe, it, expect } from 'vitest';
import { dedupeFileReads } from './file-read-dedup.js';
import type { Message } from '../../llm/openai-compat.js';

function readPair(callId: string, filePath: string, content: string, extraArgs: Record<string, unknown> = {}): Message[] {
  return [
    {
      role: 'assistant',
      tool_calls: [{
        id: callId,
        type: 'function',
        function: { name: 'Read', arguments: JSON.stringify({ file_path: filePath, ...extraArgs }) },
      }],
    },
    { role: 'tool', tool_call_id: callId, content },
  ];
}

describe('dedupeFileReads', () => {
  it('returns no-op when there are no Read calls', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(false);
    expect(result.replacedCount).toBe(0);
  });

  it('returns no-op when each file is read only once', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a/foo.ts', 'foo content'),
      ...readPair('c2', '/a/bar.ts', 'bar content'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(false);
    expect(messages[3]!.content).toBe('foo content');
    expect(messages[5]!.content).toBe('bar content');
  });

  it('replaces older Read of the same file with a placeholder', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a/foo.ts', 'first read content (large enough to matter)'),
      ...readPair('c2', '/a/foo.ts', 'second read content'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(true);
    expect(result.replacedCount).toBe(1);
    expect(messages[3]!.content).toContain('Duplicate Read of /a/foo.ts');
    expect(messages[5]!.content).toBe('second read content');
  });

  it('keeps the most recent Read and replaces all earlier reads', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a/foo.ts', 'v1'),
      ...readPair('c2', '/a/foo.ts', 'v2'),
      ...readPair('c3', '/a/foo.ts', 'v3'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.replacedCount).toBe(2);
    expect(messages[3]!.content).toContain('Duplicate Read');
    expect(messages[5]!.content).toContain('Duplicate Read');
    expect(messages[7]!.content).toBe('v3');
  });

  it('does not touch reads of different files', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a/foo.ts', 'foo'),
      ...readPair('c2', '/a/bar.ts', 'bar'),
      ...readPair('c3', '/a/baz.ts', 'baz'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(false);
  });

  it('ignores non-Read tools (Bash, Grep, Glob)', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      {
        role: 'assistant',
        tool_calls: [{
          id: 'b1', type: 'function',
          function: { name: 'Bash', arguments: JSON.stringify({ command: 'ls' }) },
        }],
      },
      { role: 'tool', tool_call_id: 'b1', content: 'output1' },
      {
        role: 'assistant',
        tool_calls: [{
          id: 'b2', type: 'function',
          function: { name: 'Bash', arguments: JSON.stringify({ command: 'ls' }) },
        }],
      },
      { role: 'tool', tool_call_id: 'b2', content: 'output2' },
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(false);
    expect(messages[3]!.content).toBe('output1');
    expect(messages[5]!.content).toBe('output2');
  });

  it('treats Reads with different offset/limit as the same logical read', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a.ts', 'lines 0-100', { offset: 0, limit: 100 }),
      ...readPair('c2', '/a.ts', 'lines 100-200', { offset: 100, limit: 100 }),
    ];
    const result = dedupeFileReads(messages);
    expect(result.replacedCount).toBe(1);
    expect(messages[3]!.content).toContain('Duplicate Read of /a.ts');
    expect(messages[5]!.content).toBe('lines 100-200');
  });

  it('is idempotent (running twice does not double-mark)', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a.ts', 'v1'),
      ...readPair('c2', '/a.ts', 'v2'),
    ];
    const first = dedupeFileReads(messages);
    expect(first.replacedCount).toBe(1);
    const second = dedupeFileReads(messages);
    expect(second.changed).toBe(false);
    expect(second.replacedCount).toBe(0);
  });

  it('handles malformed tool arguments gracefully', () => {
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      {
        role: 'assistant',
        tool_calls: [{
          id: 'c1', type: 'function',
          function: { name: 'Read', arguments: '{not valid json' },
        }],
      },
      { role: 'tool', tool_call_id: 'c1', content: 'whatever' },
      ...readPair('c2', '/a.ts', 'real read'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.changed).toBe(false);
    expect(messages[3]!.content).toBe('whatever');
    expect(messages[5]!.content).toBe('real read');
  });

  it('counts freed characters correctly', () => {
    const big = 'X'.repeat(5000);
    const messages: Message[] = [
      { role: 'system', content: 'sys' },
      { role: 'user', content: 'task' },
      ...readPair('c1', '/a.ts', big),
      ...readPair('c2', '/a.ts', 'small'),
    ];
    const result = dedupeFileReads(messages);
    expect(result.freedChars).toBeGreaterThan(4000);
  });
});
