import { describe, expect, it } from 'vitest';
import {
  buildGlobCacheKey,
  buildGrepCacheKey,
  buildOfficeCacheKey,
  buildReadCacheKey,
  buildWebFetchCacheKey,
} from './cache-key.js';

describe('buildReadCacheKey', () => {
  it('builds a fully-specified key', () => {
    expect(buildReadCacheKey({
      workspacePath: '/ws/1',
      filePath: 'output/foo.ts',
      offset: 10,
      limit: 20,
      byteOffset: 100,
      byteLength: 200,
    })).toBe('read:v1:/ws/1:output/foo.ts:10:20:100:200');
  });

  it('normalizes undefined range params to "all"', () => {
    expect(buildReadCacheKey({ workspacePath: '/ws', filePath: 'a.txt' }))
      .toBe('read:v1:/ws:a.txt:all:all:all:all');
  });

  it('keeps zero ranges distinct from undefined', () => {
    const withZero = buildReadCacheKey({ workspacePath: '/ws', filePath: 'a.txt', offset: 0, limit: 0 });
    const withAll = buildReadCacheKey({ workspacePath: '/ws', filePath: 'a.txt' });
    expect(withZero).toBe('read:v1:/ws:a.txt:0:0:all:all');
    expect(withZero).not.toBe(withAll);
  });

  it('separates identical file paths in different workspaces', () => {
    const a = buildReadCacheKey({ workspacePath: '/ws/a', filePath: 'same.txt' });
    const b = buildReadCacheKey({ workspacePath: '/ws/b', filePath: 'same.txt' });
    expect(a).not.toBe(b);
  });

  it('separates different range slices of the same file', () => {
    const a = buildReadCacheKey({ workspacePath: '/ws', filePath: 'f', offset: 1, limit: 10 });
    const b = buildReadCacheKey({ workspacePath: '/ws', filePath: 'f', offset: 2, limit: 10 });
    expect(a).not.toBe(b);
  });

  it('handles unicode file paths', () => {
    expect(buildReadCacheKey({ workspacePath: '/ws', filePath: 'output/日本語ファイル.md' }))
      .toBe('read:v1:/ws:output/日本語ファイル.md:all:all:all:all');
  });
});

describe('buildGrepCacheKey', () => {
  it('builds a fully-specified key with pattern last', () => {
    expect(buildGrepCacheKey({
      workspacePath: '/ws',
      pattern: 'foo.*bar',
      path: 'src',
      glob: '*.ts',
    })).toBe('grep:v1:/ws:src:*.ts:foo.*bar');
  });

  it('defaults path to "." and glob to "*"', () => {
    expect(buildGrepCacheKey({ workspacePath: '/ws', pattern: 'x' }))
      .toBe('grep:v1:/ws:.:*:x');
  });

  it('keeps patterns containing colons stable (pattern is the final segment)', () => {
    expect(buildGrepCacheKey({ workspacePath: '/ws', pattern: 'a:b:c' }))
      .toBe('grep:v1:/ws:.:*:a:b:c');
  });

  it('distinguishes same pattern under different globs', () => {
    const a = buildGrepCacheKey({ workspacePath: '/ws', pattern: 'p', glob: '*.ts' });
    const b = buildGrepCacheKey({ workspacePath: '/ws', pattern: 'p', glob: '*.js' });
    expect(a).not.toBe(b);
  });
});

describe('buildGlobCacheKey', () => {
  it('builds key with explicit path', () => {
    expect(buildGlobCacheKey({ workspacePath: '/ws', pattern: '**/*.ts', path: 'src' }))
      .toBe('glob:v1:/ws:src:**/*.ts');
  });

  it('defaults path to "."', () => {
    expect(buildGlobCacheKey({ workspacePath: '/ws', pattern: '*.md' }))
      .toBe('glob:v1:/ws:.:*.md');
  });

  it('distinguishes glob from grep keys for similar args', () => {
    const glob = buildGlobCacheKey({ workspacePath: '/ws', pattern: 'p' });
    const grep = buildGrepCacheKey({ workspacePath: '/ws', pattern: 'p' });
    expect(glob).not.toBe(grep);
  });
});

describe('buildWebFetchCacheKey', () => {
  it('lower-cases scheme and host', () => {
    expect(buildWebFetchCacheKey({ url: 'HTTPS://Example.COM/Path' }))
      .toBe('webfetch:v1:https://example.com/Path');
  });

  it('drops URL fragments', () => {
    expect(buildWebFetchCacheKey({ url: 'https://example.com/page#section-2' }))
      .toBe('webfetch:v1:https://example.com/page');
  });

  it('preserves the query string', () => {
    expect(buildWebFetchCacheKey({ url: 'https://example.com/search?q=a&b=2' }))
      .toBe('webfetch:v1:https://example.com/search?q=a&b=2');
  });

  it('normalizes bare host and trailing-slash host to the same key', () => {
    const bare = buildWebFetchCacheKey({ url: 'https://example.com' });
    const slash = buildWebFetchCacheKey({ url: 'https://example.com/' });
    expect(bare).toBe(slash);
  });

  it('preserves path case (only scheme/host are case-insensitive)', () => {
    const a = buildWebFetchCacheKey({ url: 'https://example.com/A' });
    const b = buildWebFetchCacheKey({ url: 'https://example.com/a' });
    expect(a).not.toBe(b);
  });

  it('keys invalid URLs on themselves', () => {
    expect(buildWebFetchCacheKey({ url: 'not a url' }))
      .toBe('webfetch:v1:not a url');
    expect(buildWebFetchCacheKey({ url: '' }))
      .toBe('webfetch:v1:');
  });

  it('treats same URL with different fragments as the same entry', () => {
    const a = buildWebFetchCacheKey({ url: 'https://x.test/p#one' });
    const b = buildWebFetchCacheKey({ url: 'https://x.test/p#two' });
    expect(a).toBe(b);
  });
});

describe('buildOfficeCacheKey', () => {
  it('builds key with explicit range', () => {
    expect(buildOfficeCacheKey({
      workspacePath: '/ws',
      toolName: 'ReadExcel',
      filePath: 'input/book.xlsx',
      range: 'sheet=Sheet1;rows=1-100',
    })).toBe('office:v1:ReadExcel:/ws:input/book.xlsx:sheet=Sheet1;rows=1-100');
  });

  it('defaults range to "all"', () => {
    expect(buildOfficeCacheKey({ workspacePath: '/ws', toolName: 'ReadPdf', filePath: 'a.pdf' }))
      .toBe('office:v1:ReadPdf:/ws:a.pdf:all');
  });

  it('separates same file across different office tools', () => {
    const pdf = buildOfficeCacheKey({ workspacePath: '/ws', toolName: 'ReadPdf', filePath: 'f' });
    const docx = buildOfficeCacheKey({ workspacePath: '/ws', toolName: 'ReadDocx', filePath: 'f' });
    expect(pdf).not.toBe(docx);
  });

  it('separates same tool+file across workspaces', () => {
    const a = buildOfficeCacheKey({ workspacePath: '/ws/a', toolName: 'ReadPdf', filePath: 'f' });
    const b = buildOfficeCacheKey({ workspacePath: '/ws/b', toolName: 'ReadPdf', filePath: 'f' });
    expect(a).not.toBe(b);
  });
});

describe('cache key version prefix', () => {
  it('every formula embeds the v1 version tag right after the tool tag', () => {
    expect(buildReadCacheKey({ workspacePath: 'w', filePath: 'f' }).startsWith('read:v1:')).toBe(true);
    expect(buildGrepCacheKey({ workspacePath: 'w', pattern: 'p' }).startsWith('grep:v1:')).toBe(true);
    expect(buildGlobCacheKey({ workspacePath: 'w', pattern: 'p' }).startsWith('glob:v1:')).toBe(true);
    expect(buildWebFetchCacheKey({ url: 'https://a.test/' }).startsWith('webfetch:v1:')).toBe(true);
    expect(buildOfficeCacheKey({ workspacePath: 'w', toolName: 't', filePath: 'f' }).startsWith('office:v1:')).toBe(true);
  });
});
