/**
 * Unit tests for API formatters (TRUEREF-0010).
 *
 * Covers state mapping, library search JSON formatting, and context
 * JSON/txt response formatting.
 */

import { describe, it, expect } from 'vitest';
import {
	mapState,
	formatLibrarySearchJson,
	formatContextJson,
	formatContextTxt
} from './formatters';
import type { LibrarySearchResult } from '$lib/server/search/search.service';
import type { SnippetSearchResult } from '$lib/server/search/search.service';
import type { Repository, RepositoryVersion, Snippet } from '$lib/types';

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

function makeRepo(overrides: Partial<Repository> = {}): Repository {
	return {
		id: '/facebook/react',
		title: 'React',
		description: 'A JavaScript library for building user interfaces',
		source: 'github',
		sourceUrl: 'https://github.com/facebook/react',
		branch: 'main',
		state: 'indexed',
		totalSnippets: 1247,
		totalTokens: 142000,
		trustScore: 9.2,
		benchmarkScore: 87,
		stars: 228000,
		githubToken: null,
		lastIndexedAt: new Date('2026-03-22T10:00:00Z'),
		createdAt: new Date('2024-01-01T00:00:00Z'),
		updatedAt: new Date('2026-03-22T10:00:00Z'),
		...overrides
	};
}

function makeVersion(tag: string): RepositoryVersion {
	return {
		id: `/facebook/react/${tag}`,
		repositoryId: '/facebook/react',
		tag,
		title: null,
		state: 'indexed',
		totalSnippets: 100,
		indexedAt: new Date(),
		createdAt: new Date()
	};
}

function makeSnippet(overrides: Partial<Snippet> = {}): Snippet {
	return {
		id: 'snippet-1',
		documentId: 'doc-1',
		repositoryId: '/facebook/react',
		versionId: null,
		type: 'code',
		title: 'Basic Component',
		content: 'function MyComponent() {\n  return <div>Hello</div>;\n}',
		language: 'tsx',
		breadcrumb: 'Getting Started > Components',
		tokenCount: 45,
		createdAt: new Date(),
		...overrides
	};
}

function makeSnippetResult(snippet: Snippet): SnippetSearchResult {
	return {
		snippet,
		score: -1.5,
		repository: { id: snippet.repositoryId, title: 'React' }
	};
}

// ---------------------------------------------------------------------------
// mapState
// ---------------------------------------------------------------------------

describe('mapState', () => {
	it('maps indexed → finalized', () => {
		expect(mapState('indexed')).toBe('finalized');
	});

	it('maps pending → initial', () => {
		expect(mapState('pending')).toBe('initial');
	});

	it('maps indexing → initial', () => {
		expect(mapState('indexing')).toBe('initial');
	});

	it('maps error → error', () => {
		expect(mapState('error')).toBe('error');
	});
});

// ---------------------------------------------------------------------------
// formatLibrarySearchJson
// ---------------------------------------------------------------------------

describe('formatLibrarySearchJson', () => {
	it('returns results array with correct shape', () => {
		const results: LibrarySearchResult[] = [
			{
				repository: makeRepo(),
				versions: [makeVersion('v18.3.0'), makeVersion('v17.0.2')],
				score: 150
			}
		];

		const response = formatLibrarySearchJson(results);

		expect(response.results).toHaveLength(1);
		const r = response.results[0];
		expect(r.id).toBe('/facebook/react');
		expect(r.title).toBe('React');
		expect(r.state).toBe('finalized');
		expect(r.totalTokens).toBe(142000);
		expect(r.totalSnippets).toBe(1247);
		expect(r.versions).toEqual(['v18.3.0', 'v17.0.2']);
		expect(r.stars).toBe(228000);
		expect(r.lastUpdateDate).toBe('2026-03-22T10:00:00.000Z');
		expect(r.source).toBe('https://github.com/facebook/react');
	});

	it('returns empty results array when no results', () => {
		const response = formatLibrarySearchJson([]);
		expect(response.results).toEqual([]);
	});

	it('maps non-indexed state to initial', () => {
		const results: LibrarySearchResult[] = [
			{ repository: makeRepo({ state: 'pending' }), versions: [], score: 0 }
		];
		const response = formatLibrarySearchJson(results);
		expect(response.results[0].state).toBe('initial');
	});

	it('handles null lastIndexedAt', () => {
		const results: LibrarySearchResult[] = [
			{ repository: makeRepo({ lastIndexedAt: null }), versions: [], score: 0 }
		];
		const response = formatLibrarySearchJson(results);
		expect(response.results[0].lastUpdateDate).toBeNull();
	});
});

// ---------------------------------------------------------------------------
// formatContextJson
// ---------------------------------------------------------------------------

describe('formatContextJson', () => {
	it('formats code snippets correctly', () => {
		const snippet = makeSnippet({ type: 'code' });
		const results = [makeSnippetResult(snippet)];

		const response = formatContextJson(results, []);

		expect(response.snippets).toHaveLength(1);
		const s = response.snippets[0];
		expect(s.type).toBe('code');
		if (s.type === 'code') {
			expect(s.title).toBe('Basic Component');
			expect(s.language).toBe('tsx');
			expect(s.codeList).toHaveLength(1);
			expect(s.codeList[0].code).toContain('MyComponent');
			expect(s.pageTitle).toBe('Getting Started');
		}
	});

	it('formats info snippets correctly', () => {
		const snippet = makeSnippet({
			id: 'info-1',
			type: 'info',
			title: null,
			content: 'React components let you split the UI...',
			language: null,
			breadcrumb: 'Core Concepts > Components'
		});
		const results = [makeSnippetResult(snippet)];

		const response = formatContextJson(results, []);

		const s = response.snippets[0];
		expect(s.type).toBe('info');
		if (s.type === 'info') {
			expect(s.text).toContain('React components');
			expect(s.breadcrumb).toBe('Core Concepts > Components');
			expect(s.pageId).toBe('info-1');
		}
	});

	it('includes rules in response', () => {
		const rules = ['Always use functional components', 'Use hooks for state management'];
		const response = formatContextJson([], rules);
		expect(response.rules).toEqual(rules);
	});

	it('computes totalTokens correctly', () => {
		const snippets = [
			makeSnippetResult(makeSnippet({ id: 'a', tokenCount: 100 })),
			makeSnippetResult(makeSnippet({ id: 'b', tokenCount: 200 }))
		];
		const response = formatContextJson(snippets, []);
		expect(response.totalTokens).toBe(300);
	});

	it('handles null tokenCount in totalTokens sum', () => {
		const snippets = [makeSnippetResult(makeSnippet({ tokenCount: null }))];
		const response = formatContextJson(snippets, []);
		expect(response.totalTokens).toBe(0);
	});
});

// ---------------------------------------------------------------------------
// formatContextTxt
// ---------------------------------------------------------------------------

describe('formatContextTxt', () => {
	it('prepends rules section when rules are present', () => {
		const rules = ['Always use functional components'];
		const txt = formatContextTxt([], rules);
		expect(txt).toContain('## Library Rules');
		expect(txt).toContain('- Always use functional components');
	});

	it('omits rules section when no rules', () => {
		const snippet = makeSnippet();
		const txt = formatContextTxt([makeSnippetResult(snippet)], []);
		expect(txt).not.toContain('## Library Rules');
	});

	it('formats code snippets with fenced code block', () => {
		const snippet = makeSnippet({ type: 'code', language: 'tsx' });
		const txt = formatContextTxt([makeSnippetResult(snippet)], []);
		expect(txt).toContain('```tsx');
		expect(txt).toContain('MyComponent');
		expect(txt).toContain('```');
	});

	it('formats info snippets as plain text', () => {
		const snippet = makeSnippet({
			type: 'info',
			content: 'React is a UI library.',
			language: null
		});
		const txt = formatContextTxt([makeSnippetResult(snippet)], []);
		expect(txt).toContain('React is a UI library.');
		expect(txt).not.toContain('```');
	});

	it('includes breadcrumb as italic line', () => {
		const snippet = makeSnippet({ breadcrumb: 'Getting Started > Components' });
		const txt = formatContextTxt([makeSnippetResult(snippet)], []);
		expect(txt).toContain('*Getting Started > Components*');
	});

	it('separates snippets with ---', () => {
		const s1 = makeSnippetResult(makeSnippet({ id: 'a' }));
		const s2 = makeSnippetResult(makeSnippet({ id: 'b', type: 'info', content: 'hello' }));
		const txt = formatContextTxt([s1, s2], []);
		expect(txt).toContain('---');
	});

	it('returns empty string for empty inputs with no rules', () => {
		const txt = formatContextTxt([], []);
		expect(txt).toBe('');
	});
});
