/// <reference types="bun-types" />

import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import { mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { runRg, runRgCount } from './cli';
import { grep } from './tools';
import { formatGrepResult } from './utils';

describe('grep tool', () => {
  const testDir = join(tmpdir(), `grep-test-${Date.now()}`);
  const testFile1 = join(testDir, 'test1.txt');
  const testFile2 = join(testDir, 'test2.ts');

  beforeAll(async () => {
    await mkdir(testDir, { recursive: true });
    await writeFile(
      testFile1,
      'Hello world\nThis is a test file\nAnother line with match',
    );
    await writeFile(
      testFile2,
      "const x = 'Hello world';\nconsole.log('test');",
    );
  });

  afterAll(async () => {
    await rm(testDir, { recursive: true, force: true });
  });

  describe('formatGrepResult', () => {
    test('formats empty results', () => {
      const result = {
        matches: [],
        totalMatches: 0,
        filesSearched: 10,
        truncated: false,
      };
      expect(formatGrepResult(result)).toBe('No matches found.');
    });

    test('formats error results', () => {
      const result = {
        matches: [],
        totalMatches: 0,
        filesSearched: 0,
        truncated: false,
        error: 'Something went wrong',
      };
      expect(formatGrepResult(result)).toBe('Error: Something went wrong');
    });

    test('formats matches correctly', () => {
      const result = {
        matches: [
          { file: 'file1.ts', line: 10, text: "const foo = 'bar'" },
          { file: 'file1.ts', line: 15, text: 'console.log(foo)' },
          { file: 'file2.ts', line: 5, text: "import { foo } from './file1'" },
        ],
        totalMatches: 3,
        filesSearched: 2,
        truncated: false,
      };

      const output = formatGrepResult(result);
      expect(output).toContain('file1.ts:');
      expect(output).toContain("  10: const foo = 'bar'");
      expect(output).toContain('  15: console.log(foo)');
      expect(output).toContain('file2.ts:');
      expect(output).toContain("  5: import { foo } from './file1'");
      expect(output).toContain('Found 3 matches in 2 files');
    });

    test('indicates truncation', () => {
      const result = {
        matches: [{ file: 'foo.txt', line: 1, text: 'bar' }],
        totalMatches: 100,
        filesSearched: 50,
        truncated: true,
      };
      expect(formatGrepResult(result)).toContain('(output truncated)');
    });
  });

  describe('runRg', () => {
    test('finds matches in files', async () => {
      const result = await runRg({
        pattern: 'Hello',
        paths: [testDir],
      });

      expect(result.totalMatches).toBeGreaterThanOrEqual(2);
      expect(
        result.matches.some(
          (m) => m.file.includes('test1.txt') && m.text.includes('Hello'),
        ),
      ).toBe(true);
      expect(
        result.matches.some(
          (m) => m.file.includes('test2.ts') && m.text.includes('Hello'),
        ),
      ).toBe(true);
    });

    test('respects file inclusion patterns', async () => {
      const result = await runRg({
        pattern: 'Hello',
        paths: [testDir],
        globs: ['*.txt'],
      });

      expect(result.matches.some((m) => m.file.includes('test1.txt'))).toBe(
        true,
      );
      expect(result.matches.some((m) => m.file.includes('test2.ts'))).toBe(
        false,
      );
    });

    test('handles no matches', async () => {
      const result = await runRg({
        pattern: 'NonExistentString12345',
        paths: [testDir],
      });

      expect(result.totalMatches).toBe(0);
      expect(result.matches).toHaveLength(0);
    });

    test('respects case sensitivity', async () => {
      // Test with exact case match
      const resultExact = await runRg({
        pattern: 'Hello',
        paths: [testDir],
      });
      expect(resultExact.totalMatches).toBeGreaterThan(0);

      // Test with caseSensitive flag set to true (should not match lowercase pattern)
      const resultSensitive = await runRg({
        pattern: 'hello', // File has "Hello"
        paths: [testDir],
        caseSensitive: true,
      });
      expect(resultSensitive.totalMatches).toBe(0);
    });

    test('respects whole word match', async () => {
      const resultPartial = await runRg({
        pattern: 'Hell',
        paths: [testDir],
        wholeWord: false,
      });
      expect(resultPartial.totalMatches).toBeGreaterThan(0);

      const resultWhole = await runRg({
        pattern: 'Hell',
        paths: [testDir],
        wholeWord: true,
      });
      expect(resultWhole.totalMatches).toBe(0);
    });

    test('respects max count', async () => {
      const result = await runRg({
        pattern: 'Hello',
        paths: [testDir],
        maxCount: 1,
      });
      // maxCount is per file
      expect(
        result.matches.filter((m) => m.file.includes('test1.txt')).length,
      ).toBeLessThanOrEqual(1);
    });
  });

  describe('runRgCount', () => {
    test('counts matches correctly', async () => {
      const results = await runRgCount({
        pattern: 'Hello',
        paths: [testDir],
      });

      expect(results.length).toBeGreaterThan(0);
      const file1Result = results.find((r) => r.file.includes('test1.txt'));
      expect(file1Result).toBeDefined();
      expect(file1Result?.count).toBe(1);
    });
  });

  describe('grep tool execute', () => {
    test('executes successfully', async () => {
      // @ts-expect-error
      const result = await grep.execute({
        pattern: 'Hello',
        path: testDir,
      });

      expect(typeof result).toBe('string');
      expect(result).toContain('Found');
      expect(result).toContain('matches');
    });

    test('handles errors gracefully', async () => {
      // @ts-expect-error
      const result = await grep.execute({
        pattern: 'Hello',
        path: '/non/existent/path/12345',
      });

      // Depending on implementation, it might return "No matches found" or an error string
      // But it should not throw
      expect(typeof result).toBe('string');
    });

    test('respects include pattern in execute', async () => {
      // @ts-expect-error
      const result = await grep.execute({
        pattern: 'Hello',
        path: testDir,
        include: '*.txt',
      });

      expect(result).toContain('test1.txt');
      expect(result).not.toContain('test2.ts');
    });
  });
});
