import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { detectRecipe, parseRecipe } from '$lib/server/parser';
import * as logger from '$lib/server/utils/logger';
import * as llm from '$lib/server/llm';

describe('parser.ts logging', () => {
	let logErrorSpy: any;
	let consoleErrorSpy: any;

	beforeEach(() => {
		logErrorSpy = vi.spyOn(logger, 'logError');
		consoleErrorSpy = vi.spyOn(console, 'error');

		// Mock LLM module to always throw errors for testing error logging
		vi.spyOn(llm, 'createLLM').mockReturnValue({
			client: {
				chat: {
					completions: {
						create: vi.fn().mockRejectedValue(new Error('LLM detection error'))
					}
				},
				beta: {
					chat: {
						completions: {
							parse: vi.fn().mockRejectedValue(new Error('LLM parse error'))
						}
					}
				}
			} as any,
			model: 'test-model'
		});

		vi.spyOn(llm, 'checkModelAvailability').mockResolvedValue({
			available: true,
			message: 'Model available'
		});
	});

	afterEach(() => {
		vi.restoreAllMocks();
	});

	test('detectRecipe should use logError on failure', async () => {
		try {
			await detectRecipe('test text');
		} catch (e) {
			// Expected to throw
		}

		expect(logErrorSpy).toHaveBeenCalledWith('[LLM] Recipe detection error', expect.any(Error));
	});

	test('parseRecipe should use logError on failure', async () => {
		try {
			await parseRecipe('test text');
		} catch (e) {
			// Expected to throw
		}

		expect(logErrorSpy).toHaveBeenCalledWith('[LLM] Recipe parsing error', expect.any(Error));
	});

	test('should not log stack trace separately', async () => {
		try {
			await detectRecipe('test');
		} catch (e) {
			// Expected to throw
		}

		const stackCalls = consoleErrorSpy.mock.calls.filter((call: any) =>
			call[0]?.includes('Stack trace')
		);

		expect(stackCalls).toHaveLength(0);
	});

	test('logs should not contain [object Object]', async () => {
		try {
			await detectRecipe('test text');
		} catch (e) {
			// Expected to throw
		}

		try {
			await parseRecipe('test text');
		} catch (e) {
			// Expected to throw
		}

		// Check all console.error calls for [object Object]
		const errorCalls = consoleErrorSpy.mock.calls
			.map((call: any) => call.join(' '))
			.filter((msg: string) => msg.includes('[object Object]'));

		expect(errorCalls).toHaveLength(0);
	});

	test('logError should serialize error properly', async () => {
		const testError = new Error('Test error message');
		(testError as any).customProperty = 'custom value';

		try {
			await detectRecipe('test');
		} catch (e) {
			// Expected to throw
		}

		// Verify logError was called with error object
		expect(logErrorSpy).toHaveBeenCalled();
		const errorArg = logErrorSpy.mock.calls[0][1];
		expect(errorArg).toBeInstanceOf(Error);
	});

	test('both detectRecipe and parseRecipe should use logError', async () => {
		logErrorSpy.mockClear();

		try {
			await detectRecipe('test text');
		} catch (e) {
			// Expected
		}

		expect(logErrorSpy).toHaveBeenCalledTimes(1);

		logErrorSpy.mockClear();

		try {
			await parseRecipe('test text');
		} catch (e) {
			// Expected
		}

		expect(logErrorSpy).toHaveBeenCalledTimes(1);
	});
});
