import { describe, it, expect } from 'vitest';
import { parseRipgrepJson } from '../../../../octocode-tools-core/src/utils/parsers/ripgrep.js';
import type { RipgrepQuery } from '../../../../octocode-tools-core/src/tools/local_ripgrep/scheme.js';

const baseQuery = {
  keywords: 'test',
  path: '/test/path',
} as RipgrepQuery;

describe('parseRipgrepJson', () => {
  it('should parse basic match output', () => {
    const jsonOutput = JSON.stringify({
      type: 'match',
      data: {
        path: { text: '/test/file.ts' },
        lines: { text: 'const test = 1;' },
        line_number: 10,
        absolute_offset: 100,
        submatches: [{ match: { text: 'test' }, start: 6, end: 10 }],
      },
    });

    const { files, stats } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
    expect(files[0]!.path).toBe('/test/file.ts');
    expect(files[0]!.matchCount).toBe(1);
    expect(files[0]!.matches![0]!.line).toBe(10);
    expect(files[0]!.matches![0]!.column).toBe(6);
    expect(stats).toEqual({});
  });

  it('should parse multiple matches in the same file', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test line 1' },
          line_number: 10,
          absolute_offset: 100,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test line 2' },
          line_number: 20,
          absolute_offset: 200,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
    expect(files[0]!.matchCount).toBe(2);
    expect(files[0]!.matches).toHaveLength(2);
  });

  it('should parse matches across multiple files', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file1.ts' },
          lines: { text: 'test in file 1' },
          line_number: 10,
          absolute_offset: 100,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file2.ts' },
          lines: { text: 'test in file 2' },
          line_number: 5,
          absolute_offset: 50,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(2);
  });

  it('should parse context lines', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context before' },
          line_number: 9,
          absolute_offset: 80,
        },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test match' },
          line_number: 10,
          absolute_offset: 100,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context after' },
          line_number: 11,
          absolute_offset: 120,
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, {
      ...baseQuery,
      contextLines: 1,
    });

    expect(files).toHaveLength(1);
    expect(files[0]!.matches![0]!.value).toContain('context before');
    expect(files[0]!.matches![0]!.value).toContain('test match');
    expect(files[0]!.matches![0]!.value).toContain('context after');
  });

  it('should parse summary statistics', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test' },
          line_number: 1,
          absolute_offset: 0,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'summary',
        data: {
          elapsed_total: { human: '0.001s' },
          stats: {
            elapsed: { human: '0.001s' },
            searches: 10,
            searches_with_match: 3,
            bytes_searched: 1000,
            bytes_printed: 50,
            matched_lines: 5,
            matches: 7,
          },
        },
      }),
    ].join('\n');

    const { stats } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(stats.matchCount).toBe(7);
    expect(stats.matchedLines).toBe(5);
    expect(stats.filesMatched).toBe(3);
    expect(stats.filesSearched).toBe(10);
    expect(stats.bytesSearched).toBe(1000);
    expect(stats.searchTime).toBe('0.001s');
  });

  it('should handle empty submatches array', () => {
    const jsonOutput = JSON.stringify({
      type: 'match',
      data: {
        path: { text: '/test/file.ts' },
        lines: { text: 'test line' },
        line_number: 10,
        absolute_offset: 100,
        submatches: [],
      },
    });

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
    expect(files[0]!.matches![0]!.column).toBe(0);
  });

  it('should truncate long match values', () => {
    const longContent = 'x'.repeat(500);
    const jsonOutput = JSON.stringify({
      type: 'match',
      data: {
        path: { text: '/test/file.ts' },
        lines: { text: longContent },
        line_number: 10,
        absolute_offset: 100,
        submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
      },
    });

    const { files } = parseRipgrepJson(jsonOutput, {
      ...baseQuery,
      matchContentLength: 100,
    });

    expect(files[0]!.matches![0]!.value!.length).toBeLessThanOrEqual(100);
    expect(files[0]!.matches![0]!.value).toMatch(/\.\.\.$/);
  });

  it('should skip malformed JSON lines', () => {
    const jsonOutput = [
      'this is not json',
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test' },
          line_number: 1,
          absolute_offset: 0,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      'also not json {broken',
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
  });

  it('should skip non-JSON lines (like output headers)', () => {
    const jsonOutput = [
      'Searching...',
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test' },
          line_number: 1,
          absolute_offset: 0,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
  });

  it('should handle empty output', () => {
    const { files, stats } = parseRipgrepJson('', baseQuery);

    expect(files).toHaveLength(0);
    expect(stats).toEqual({});
  });

  it('should skip valid JSON that fails schema validation (line 46 branch)', () => {
    const jsonOutput = [
      JSON.stringify({ type: 'unknown_ripgrep_event', data: { path: {} } }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test' },
          line_number: 1,
          absolute_offset: 0,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
  });

  it('should use contextLines when specific before/after not set', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context before' },
          line_number: 8,
          absolute_offset: 60,
        },
      }),
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context before 2' },
          line_number: 9,
          absolute_offset: 80,
        },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test match' },
          line_number: 10,
          absolute_offset: 100,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, {
      ...baseQuery,
      contextLines: 2,
    });

    expect(files[0]!.matches![0]!.value).toContain('context before');
    expect(files[0]!.matches![0]!.value).toContain('context before 2');
  });

  it('should not double-space snippets when ripgrep JSON lines include trailing newlines', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context before\n' },
          line_number: 9,
          absolute_offset: 80,
        },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test match\n' },
          line_number: 10,
          absolute_offset: 100,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'context',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'context after\n' },
          line_number: 11,
          absolute_offset: 120,
        },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, {
      ...baseQuery,
      contextLines: 1,
    });

    expect(files[0]!.matches![0]!.value).toBe(
      ['context before', 'test match', 'context after'].join('\n')
    );
  });

  it('should handle begin/end messages (ignored)', () => {
    const jsonOutput = [
      JSON.stringify({
        type: 'begin',
        data: { path: { text: '/test/file.ts' } },
      }),
      JSON.stringify({
        type: 'match',
        data: {
          path: { text: '/test/file.ts' },
          lines: { text: 'test' },
          line_number: 1,
          absolute_offset: 0,
          submatches: [{ match: { text: 'test' }, start: 0, end: 4 }],
        },
      }),
      JSON.stringify({
        type: 'end',
        data: { path: { text: '/test/file.ts' } },
      }),
    ].join('\n');

    const { files } = parseRipgrepJson(jsonOutput, baseQuery);

    expect(files).toHaveLength(1);
  });
});
