/**
 * Unit tests for the code file parser (TRUEREF-0005).
 */

import { describe, it, expect } from 'vitest';
import { parseCodeFile, BOUNDARY_PATTERNS } from './code.parser.js';
import { estimateTokens, MAX_TOKENS } from './chunker.js';
import { parseFile } from './index.js';
import type { CrawledFile } from '$lib/server/crawler/types.js';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeFile(path: string, content: string, language = 'typescript'): CrawledFile {
	return { path, content, size: content.length, sha: 'abc123', language };
}

// ---------------------------------------------------------------------------
// TypeScript / JavaScript boundary detection
// ---------------------------------------------------------------------------

describe('parseCodeFile — TypeScript', () => {
	it('splits at function boundaries', () => {
		const content = `
export function foo(): string {
  return 'foo';
}

export function bar(x: number): number {
  return x * 2;
}
`.trim();

		const snippets = parseCodeFile(content, 'utils.ts', 'typescript');
		expect(snippets.length).toBeGreaterThanOrEqual(2);
		expect(snippets.every((s) => s.type === 'code')).toBe(true);
		expect(snippets.some((s) => s.content.includes('function foo'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('function bar'))).toBe(true);
	});

	it('splits at class boundaries', () => {
		const content = `
export class Greeter {
  greet(name: string) {
    return \`Hello, \${name}\`;
  }
}

export class Farewell {
  bye(name: string) {
    return \`Goodbye, \${name}\`;
  }
}
`.trim();

		const snippets = parseCodeFile(content, 'greet.ts', 'typescript');
		expect(snippets.length).toBeGreaterThanOrEqual(2);
		expect(snippets.some((s) => s.content.includes('class Greeter'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('class Farewell'))).toBe(true);
	});

	it('sets correct metadata on snippets', () => {
		const content = `
export function example(): void {
  console.log('example function body here');
}
`.trim();

		const snippets = parseCodeFile(content, 'src/utils.ts', 'typescript');
		expect(snippets.length).toBeGreaterThanOrEqual(1);
		const s = snippets[0]!;
		expect(s.type).toBe('code');
		expect(s.language).toBe('typescript');
		expect(s.title).toBe('utils.ts');
		expect(s.breadcrumb).toBe('src/utils.ts');
	});

	it('produces at least one snippet from a file with many small declarations', () => {
		// Each block: a multi-line function — boundary detection fires but chunks are
		// large enough to survive the MIN_CONTENT_LENGTH filter.
		const blocks = Array.from(
			{ length: 10 },
			(_, i) => `export function helper${i}(x: number): number {\n  return x + ${i};\n}`
		);
		const content = blocks.join('\n\n');

		const snippets = parseCodeFile(content, 'generated.ts', 'typescript');
		expect(snippets.length).toBeGreaterThanOrEqual(1);
		expect(snippets.every((s) => s.type === 'code')).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Python
// ---------------------------------------------------------------------------

describe('parseCodeFile — Python', () => {
	it('splits at def and class boundaries', () => {
		const content = `
def greet(name):
    return f"Hello, {name}"

class MyClass:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

async def fetch_data(url):
    return await http.get(url)
`.trim();

		const snippets = parseCodeFile(content, 'app.py', 'python');
		expect(snippets.some((s) => s.content.includes('def greet'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('class MyClass'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('async def fetch_data'))).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Go
// ---------------------------------------------------------------------------

describe('parseCodeFile — Go', () => {
	it('splits at func boundaries', () => {
		const content = `
package main

import "fmt"

func greet(name string) string {
	return fmt.Sprintf("Hello, %s", name)
}

func main() {
	fmt.Println(greet("world"))
}
`.trim();

		const snippets = parseCodeFile(content, 'main.go', 'go');
		expect(snippets.some((s) => s.content.includes('func greet'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('func main'))).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Rust
// ---------------------------------------------------------------------------

describe('parseCodeFile — Rust', () => {
	it('splits at fn and struct boundaries', () => {
		const content = `
pub struct Config {
    pub name: String,
    pub value: u32,
}

pub fn create_config(name: &str, value: u32) -> Config {
    Config { name: name.to_string(), value }
}

impl Config {
    pub fn new() -> Self {
        Config { name: String::new(), value: 0 }
    }
}
`.trim();

		const snippets = parseCodeFile(content, 'config.rs', 'rust');
		expect(snippets.some((s) => s.content.includes('pub struct Config'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('pub fn create_config'))).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Ruby
// ---------------------------------------------------------------------------

describe('parseCodeFile — Ruby', () => {
	it('splits at def and class boundaries', () => {
		const content = `
class Greeter
  def initialize(name)
    @name = name
  end

  def greet
    "Hello, #{@name}!"
  end
end

def standalone_helper
  puts "helper"
end
`.trim();

		const snippets = parseCodeFile(content, 'greeter.rb', 'ruby');
		expect(snippets.some((s) => s.content.includes('class Greeter'))).toBe(true);
		expect(snippets.some((s) => s.content.includes('def standalone_helper'))).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Config / data files
// ---------------------------------------------------------------------------

describe('parseCodeFile — JSON', () => {
	it('produces at least one code snippet from a JSON object', () => {
		const content = JSON.stringify(
			{
				name: 'my-package',
				version: '1.0.0',
				dependencies: { lodash: '^4.17.21' }
			},
			null,
			2
		);

		const snippets = parseCodeFile(content, 'package.json', 'json');
		expect(snippets.length).toBeGreaterThanOrEqual(1);
		expect(snippets.every((s) => s.type === 'code')).toBe(true);
	});
});

describe('parseCodeFile — YAML', () => {
	it('splits a YAML file at top-level keys', () => {
		const content = `
name: my-project
version: 1.0.0
scripts:
  build: tsc
  test: vitest
dependencies:
  lodash: ^4.17.21
`.trim();

		const snippets = parseCodeFile(content, 'config.yaml', 'yaml');
		expect(snippets.length).toBeGreaterThanOrEqual(1);
	});
});

// ---------------------------------------------------------------------------
// HTML-like files
// ---------------------------------------------------------------------------

describe('parseCodeFile — HTML', () => {
	it('extracts script block and text content', () => {
		const content = `
<!DOCTYPE html>
<html>
<head><title>Test Page</title></head>
<body>
  <p>This is the page body content with enough text for an info snippet.</p>
  <script>
    function init() {
      console.log('page loaded and ready for interaction');
    }
  </script>
</body>
</html>
`.trim();

		const snippets = parseCodeFile(content, 'index.html', 'html');
		expect(snippets.some((s) => s.type === 'code')).toBe(true);
		expect(snippets.some((s) => s.type === 'info')).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Plain text
// ---------------------------------------------------------------------------

describe('parseCodeFile — plain text', () => {
	it('splits on paragraph boundaries', () => {
		const content = `
This is the first paragraph with enough content to pass the minimum length check.

This is the second paragraph that also has enough content to be included here.
`.trim();

		const snippets = parseCodeFile(content, 'notes.txt', 'text');
		expect(snippets.length).toBeGreaterThanOrEqual(2);
		expect(snippets.every((s) => s.type === 'info')).toBe(true);
	});

	it('skips paragraphs shorter than 20 characters', () => {
		const content = 'Short.\n\nThis is a much longer paragraph that definitely passes the minimum length filter.';
		const snippets = parseCodeFile(content, 'notes.txt', 'text');
		expect(snippets.length).toBe(1);
	});
});

// ---------------------------------------------------------------------------
// Unknown language fallback
// ---------------------------------------------------------------------------

describe('parseCodeFile — unknown language', () => {
	it('falls back to sliding window for unrecognised languages', () => {
		const lines = Array.from({ length: 50 }, (_, i) => `line ${i}: some code content here`);
		const content = lines.join('\n');

		const snippets = parseCodeFile(content, 'script.lua', 'lua');
		expect(snippets.length).toBeGreaterThanOrEqual(1);
		expect(snippets.every((s) => s.type === 'code')).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Min content filter
// ---------------------------------------------------------------------------

describe('parseCodeFile — minimum content filter', () => {
	it('skips segments shorter than 20 characters', () => {
		const content = `
export function realFunction(): string {
  // A function with enough content to be included in the output snippets.
  return 'result value from the function that does the operation here';
}
`.trim();

		const snippets = parseCodeFile(content, 'test.ts', 'typescript');
		expect(snippets.every((s) => s.content.length >= 20)).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// Token count cap
// ---------------------------------------------------------------------------

describe('parseCodeFile — token count', () => {
	it('all snippets have tokenCount within MAX_TOKENS', () => {
		const lines = Array.from({ length: 300 }, (_, i) => `// comment line number ${i} here\nconst x${i} = ${i};`);
		const content = lines.join('\n');

		const snippets = parseCodeFile(content, 'large.ts', 'typescript');
		for (const s of snippets) {
			expect(estimateTokens(s.content)).toBeLessThanOrEqual(MAX_TOKENS + 50); // slight tolerance for boundary chunks
		}
	});
});

// ---------------------------------------------------------------------------
// parseFile integration
// ---------------------------------------------------------------------------

describe('parseFile — integration', () => {
	const opts = { repositoryId: 'repo-1', documentId: 'doc-1', versionId: 'v1' };

	it('returns NewSnippet records with all required fields for a .ts file', () => {
		const file = makeFile(
			'src/utils.ts',
			`export function add(a: number, b: number): number {\n  return a + b;\n}\n`
		);

		const snippets = parseFile(file, opts);
		expect(snippets.length).toBeGreaterThanOrEqual(1);

		for (const s of snippets) {
			expect(s.id).toBeTruthy();
			expect(s.repositoryId).toBe('repo-1');
			expect(s.documentId).toBe('doc-1');
			expect(s.versionId).toBe('v1');
			expect(s.createdAt).toBeInstanceOf(Date);
			expect(s.content).toBeTruthy();
			expect(s.type).toMatch(/^(code|info)$/);
		}
	});

	it('returns NewSnippet records for a .md file', () => {
		const file = makeFile(
			'README.md',
			`# Hello\n\nThis is a long enough paragraph to pass the minimum content length filter.\n`,
			'markdown'
		);

		const snippets = parseFile(file, opts);
		expect(snippets.length).toBeGreaterThanOrEqual(1);
		expect(snippets[0]?.type).toBe('info');
	});

	it('uses null for versionId when not provided', () => {
		const file = makeFile('src/index.ts', `export function noop(): void {}\n`);
		const snippets = parseFile(file, { repositoryId: 'r', documentId: 'd' });

		// noop is too short; file may return 0 snippets — just verify no error thrown
		expect(Array.isArray(snippets)).toBe(true);
	});
});

// ---------------------------------------------------------------------------
// BOUNDARY_PATTERNS export
// ---------------------------------------------------------------------------

describe('BOUNDARY_PATTERNS', () => {
	it('contains entries for core languages', () => {
		expect(BOUNDARY_PATTERNS['typescript']).toBeInstanceOf(RegExp);
		expect(BOUNDARY_PATTERNS['python']).toBeInstanceOf(RegExp);
		expect(BOUNDARY_PATTERNS['go']).toBeInstanceOf(RegExp);
		expect(BOUNDARY_PATTERNS['rust']).toBeInstanceOf(RegExp);
		expect(BOUNDARY_PATTERNS['ruby']).toBeInstanceOf(RegExp);
	});
});
