import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { serializeError, serializeObject, logError, logObject } from '$lib/server/utils/logger';

describe('logger utilities', () => {
	describe('serializeError', () => {
		test('handles Error objects', () => {
			const error = new Error('Test error message');
			const result = serializeError(error);

			expect(result).toContain('Test error message');
			expect(result).toContain('"name": "Error"');
			expect(result).toContain('"message"');
		});

		test('handles plain objects', () => {
			const obj = { code: 404, message: 'Not found' };
			const result = serializeError(obj);

			expect(result).toContain('"code": 404');
			expect(result).toContain('"message": "Not found"');
		});

		test('includes stack trace for Error objects', () => {
			const error = new Error('Stack test');
			const result = serializeError(error);

			expect(result).toContain('"stack"');
		});

		test('handles Error with custom properties', () => {
			const error = new Error('Custom error') as any;
			error.statusCode = 500;
			error.details = { info: 'extra data' };
			const result = serializeError(error);

			expect(result).toContain('"statusCode": 500');
			expect(result).toContain('extra data');
		});
	});

	describe('serializeObject', () => {
		test('handles circular references', () => {
			const obj: any = { a: 1, b: 2 };
			obj.self = obj;

			const result = serializeObject(obj);
			expect(result).toContain('[Circular]');
			expect(result).toContain('"a": 1');
		});

		test('handles deeply nested objects', () => {
			const obj = {
				level1: {
					level2: {
						level3: {
							value: 'deep'
						}
					}
				}
			};

			const result = serializeObject(obj);
			expect(result).toContain('"value": "deep"');
		});

		test('handles arrays', () => {
			const obj = { items: [1, 2, 3] };
			const result = serializeObject(obj);

			expect(result).toContain('"items"');
			expect(result).toContain('[');
		});

		test('handles null and undefined', () => {
			const obj = { a: null, b: undefined };
			const result = serializeObject(obj);

			expect(result).toContain('"a": null');
		});
	});

	describe('logError', () => {
		let consoleErrorSpy: any;

		beforeEach(() => {
			consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
		});

		afterEach(() => {
			consoleErrorSpy.mockRestore();
		});

		test('outputs to console.error', () => {
			const error = new Error('Test');

			logError('[Test]', error);

			expect(consoleErrorSpy).toHaveBeenCalledWith('[Test]', 'Test');
		});

		test('logs stack trace for Error objects', () => {
			const error = new Error('Stack error');

			logError('[Test]', error);

			expect(consoleErrorSpy).toHaveBeenCalledWith(
				expect.stringMatching(/Stack/),
				expect.any(String)
			);
		});

		test('handles non-Error objects', () => {
			const obj = { code: 500, message: 'Server error' };

			logError('[Test]', obj);

			expect(consoleErrorSpy).toHaveBeenCalledWith(
				'[Test]',
				expect.stringContaining('"code": 500')
			);
		});
	});

	describe('logObject', () => {
		let consoleLogSpy: any;

		beforeEach(() => {
			consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
		});

		afterEach(() => {
			consoleLogSpy.mockRestore();
		});

		test('outputs to console.log', () => {
			const obj = { key: 'value' };

			logObject('[Test]', obj);

			expect(consoleLogSpy).toHaveBeenCalledWith(
				'[Test]',
				expect.stringContaining('"key": "value"')
			);
		});

		test('handles circular references', () => {
			const obj: any = { a: 1 };
			obj.self = obj;

			logObject('[Test]', obj);

			expect(consoleLogSpy).toHaveBeenCalledWith('[Test]', expect.stringContaining('[Circular]'));
		});
	});
});
