import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { findFilesTool, grepContentLocally, grepTool } from '../src/modules/netaclaw/tools/builtin/search.js';

describe('search tools', () => {
  const tempDirs: string[] = [];

  afterEach(() => {
    tempDirs.splice(0).forEach(dir => fs.rmSync(dir, { recursive: true, force: true }));
  });

  const createFixture = () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-search-tools-'));
    tempDirs.push(root);
    fs.mkdirSync(path.join(root, 'src'), { recursive: true });
    fs.mkdirSync(path.join(root, 'node_modules', 'ignored'), { recursive: true });
    fs.writeFileSync(path.join(root, 'src', 'alpha.ts'), 'export const targetValue = 1;\n', 'utf8');
    fs.writeFileSync(path.join(root, 'src', 'beta.vue'), '<template>targetValue</template>\n', 'utf8');
    fs.writeFileSync(path.join(root, '.hidden.ts'), 'const hiddenValue = true;\n', 'utf8');
    fs.writeFileSync(path.join(root, 'node_modules', 'ignored', 'skip.ts'), 'targetValue\n', 'utf8');
    return root;
  };

  it('finds files by glob while skipping dependency directories', async () => {
    const root = createFixture();

    const result = await findFilesTool.execute('find-1', {
      path: root,
      pattern: '**/*.ts',
      limit: 10,
    });

    expect((result as any).text).toContain('src/alpha.ts');
    expect((result as any).text).not.toContain('node_modules');
  });

  it('finds files from runtime session cwd when path is omitted', async () => {
    const root = createFixture();

    const result = await findFilesTool.execute('find-1', {
      pattern: '**/*.vue',
      limit: 10,
      _netaRuntime: { sessionCwd: root },
    } as any);

    expect((result as any).text).toContain('src/beta.vue');
  });

  it('greps file contents with optional glob filtering', async () => {
    const root = createFixture();

    const result = await grepTool.execute('grep-1', {
      path: root,
      pattern: 'targetValue',
      glob: '**/*.vue',
      literal: true,
      limit: 10,
    });

    expect((result as any).text).toContain('src/beta.vue:1:<template>targetValue</template>');
    expect((result as any).text).not.toContain('alpha.ts');
  });

  it('greps file contents with context lines', async () => {
    const root = createFixture();
    fs.writeFileSync(
      path.join(root, 'src', 'context.ts'),
      ['before line', 'const targetValue = 2;', 'after line'].join('\n'),
      'utf8'
    );

    const result = await grepTool.execute('grep-context', {
      path: root,
      pattern: 'targetValue = 2',
      glob: '**/*.ts',
      literal: true,
      context: 1,
      limit: 10,
    });

    expect((result as any).text).toContain('src/context.ts-1-before line');
    expect((result as any).text).toContain('src/context.ts:2:const targetValue = 2;');
    expect((result as any).text).toContain('src/context.ts-3-after line');
  });

  it('greps from runtime session cwd when path is omitted', async () => {
    const root = createFixture();

    const result = await grepTool.execute('grep-cwd', {
      pattern: 'targetValue',
      literal: true,
      limit: 10,
      _netaRuntime: { sessionCwd: root },
    } as any);

    expect((result as any).text).toContain('src/alpha.ts:1:export const targetValue = 1;');
  });

  it('stops grep output at the match limit', async () => {
    const root = createFixture();
    fs.writeFileSync(
      path.join(root, 'src', 'many.ts'),
      Array.from({ length: 8 }, (_, index) => `targetValue ${index + 1}`).join('\n'),
      'utf8'
    );

    const result = await grepTool.execute('grep-limit', {
      path: path.join(root, 'src', 'many.ts'),
      pattern: 'targetValue',
      literal: true,
      limit: 2,
    });
    const text = (result as any).text as string;

    expect(text).toContain('many.ts:1:targetValue 1');
    expect(text).toContain('many.ts:2:targetValue 2');
    expect(text).not.toContain('many.ts:3:targetValue 3');
    expect(text).toContain('2');
    expect(text).toContain('limit');
  });

  it('includes hidden files in fallback traversal to match Pi hidden search behavior', async () => {
    const root = createFixture();

    const result = await grepTool.execute('grep-hidden', {
      path: path.join(root, '.hidden.ts'),
      pattern: 'hiddenValue',
      literal: true,
      limit: 10,
    });

    expect((result as any).text).toContain('.hidden.ts:1:const hiddenValue = true;');
  });

  it('counts grep limit by matches instead of rendered context lines in local fallback', async () => {
    const root = createFixture();
    const filePath = path.join(root, 'src', 'multi.ts');
    fs.writeFileSync(
      filePath,
      ['before one', 'targetValue first', 'after one', 'before two', 'targetValue second', 'after two'].join('\n'),
      'utf8'
    );

    const result = await grepContentLocally({
      files: [filePath],
      root,
      context: 1,
      limit: 1,
      pattern: line => line.includes('targetValue'),
    });

    expect(result.matchLimitReached).toBe(true);
    expect(result.outputLines).toEqual([
      'src/multi.ts-1-before one',
      'src/multi.ts:2:targetValue first',
      'src/multi.ts-3-after one',
    ]);
  });
});
