import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

/**
 * Unit tests for the Sentry/GlitchTip monitoring module (sentry.ts).
 *
 * Tests initialization, user context management, error capture, and
 * breadcrumb recording. Verifies RGPD compliance: no PII is sent to
 * GlitchTip (Authorization, Cookie headers are scrubbed, emails are
 * redacted from breadcrumbs).
 */

// Mock @sentry/sveltekit before importing the module
const mockInit = vi.fn();
const mockSetUser = vi.fn();
const mockSetTag = vi.fn();
const mockCaptureException = vi.fn();
const mockAddBreadcrumb = vi.fn();

vi.mock('@sentry/sveltekit', () => ({
	init: mockInit,
	setUser: mockSetUser,
	setTag: mockSetTag,
	captureException: mockCaptureException,
	addBreadcrumb: mockAddBreadcrumb
}));

describe('sentry monitoring', () => {
	let sentryModule: typeof import('$lib/monitoring/sentry');

	beforeEach(async () => {
		vi.clearAllMocks();
		vi.spyOn(console, 'warn').mockImplementation(() => {});

		vi.resetModules();
		sentryModule = await import('$lib/monitoring/sentry');
	});

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

	// ==========================================================================
	// initSentry
	// ==========================================================================
	describe('initSentry', () => {
		it('should call Sentry.init with correct configuration', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			expect(mockInit).toHaveBeenCalledOnce();
			expect(mockInit).toHaveBeenCalledWith(
				expect.objectContaining({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production',
					sampleRate: 1.0,
					tracesSampleRate: 0.0,
					sendDefaultPii: false
				})
			);
		});

		it('should not initialize Sentry when DSN is empty', () => {
			sentryModule.initSentry({
				dsn: '',
				environment: 'test'
			});

			expect(mockInit).not.toHaveBeenCalled();
			expect(console.warn).toHaveBeenCalledWith(
				'[Sentry] DSN not configured, error tracking disabled'
			);
		});

		it('should set user context when userId is provided', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production',
				userId: 'user-abc-123'
			});

			expect(mockSetUser).toHaveBeenCalledWith({ id: 'user-abc-123' });
		});

		it('should not set user context when userId is not provided', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			expect(mockSetUser).not.toHaveBeenCalled();
		});

		it('should set tenant tag when tenantId is provided', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'staging',
				tenantId: 'ecole-alpha'
			});

			expect(mockSetTag).toHaveBeenCalledWith('tenant_id', 'ecole-alpha');
		});

		it('should not set tenant tag when tenantId is not provided', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			expect(mockSetTag).not.toHaveBeenCalled();
		});

		it('should set both user and tenant when both are provided', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production',
				userId: 'user-xyz',
				tenantId: 'lycee-beta'
			});

			expect(mockSetUser).toHaveBeenCalledWith({ id: 'user-xyz' });
			expect(mockSetTag).toHaveBeenCalledWith('tenant_id', 'lycee-beta');
		});

		it('should disable performance tracing (tracesSampleRate = 0)', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			expect(mockInit).toHaveBeenCalledWith(
				expect.objectContaining({
					tracesSampleRate: 0.0
				})
			);
		});

		it('should disable sendDefaultPii for RGPD compliance', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			expect(mockInit).toHaveBeenCalledWith(
				expect.objectContaining({
					sendDefaultPii: false
				})
			);
		});

		it('should configure ignoreErrors for common non-errors', () => {
			sentryModule.initSentry({
				dsn: 'https://key@glitchtip.classeo.fr/1',
				environment: 'production'
			});

			const initConfig = mockInit.mock.calls[0]![0];
			expect(initConfig.ignoreErrors).toEqual(
				expect.arrayContaining([
					'ResizeObserver loop',
					'ResizeObserver loop limit exceeded',
					'NetworkError',
					'Failed to fetch',
					'Load failed',
					'AbortError'
				])
			);
		});

		// PII scrubbing via beforeSend
		describe('beforeSend PII scrubbing', () => {
			it('should remove Authorization header from event request', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = {
					request: {
						headers: {
							Authorization: 'Bearer secret-token',
							'Content-Type': 'application/json'
						}
					}
				};

				const result = beforeSend(event);

				expect(result.request.headers.Authorization).toBeUndefined();
				expect(result.request.headers['Content-Type']).toBe('application/json');
			});

			it('should remove Cookie header from event request', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = {
					request: {
						headers: {
							Cookie: 'session=abc123',
							Accept: 'text/html'
						}
					}
				};

				const result = beforeSend(event);

				expect(result.request.headers.Cookie).toBeUndefined();
				expect(result.request.headers.Accept).toBe('text/html');
			});

			it('should redact email-like strings from breadcrumb messages', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = {
					breadcrumbs: [
						{ message: 'Login failed for user@example.com', category: 'auth' },
						{ message: 'Page loaded', category: 'navigation' },
						{ message: 'Error with admin@school.fr account', category: 'auth' }
					]
				};

				const result = beforeSend(event);

				expect(result.breadcrumbs[0].message).toBe('[EMAIL_REDACTED]');
				expect(result.breadcrumbs[1].message).toBe('Page loaded');
				expect(result.breadcrumbs[2].message).toBe('[EMAIL_REDACTED]');
			});

			it('should pass through events without request or breadcrumbs', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = { message: 'Simple error' };

				const result = beforeSend(event);

				expect(result).toEqual({ message: 'Simple error' });
			});

			it('should handle event with request but no headers', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = { request: { url: 'https://example.com' } };

				const result = beforeSend(event);

				expect(result).toEqual({ request: { url: 'https://example.com' } });
			});

			it('should handle breadcrumbs without message', () => {
				sentryModule.initSentry({
					dsn: 'https://key@glitchtip.classeo.fr/1',
					environment: 'production'
				});

				const beforeSend = mockInit.mock.calls[0]![0].beforeSend;
				const event = {
					breadcrumbs: [
						{ category: 'http', data: { url: '/api/test' } }
					]
				};

				const result = beforeSend(event);

				expect(result.breadcrumbs[0].category).toBe('http');
			});
		});
	});

	// ==========================================================================
	// setUserContext
	// ==========================================================================
	describe('setUserContext', () => {
		it('should set user with ID only (no PII)', () => {
			sentryModule.setUserContext('user-abc-123');

			expect(mockSetUser).toHaveBeenCalledWith({ id: 'user-abc-123' });
		});

		it('should set tenant tag when tenantId is provided', () => {
			sentryModule.setUserContext('user-abc-123', 'ecole-alpha');

			expect(mockSetUser).toHaveBeenCalledWith({ id: 'user-abc-123' });
			expect(mockSetTag).toHaveBeenCalledWith('tenant_id', 'ecole-alpha');
		});

		it('should not set tenant tag when tenantId is not provided', () => {
			sentryModule.setUserContext('user-abc-123');

			expect(mockSetTag).not.toHaveBeenCalled();
		});
	});

	// ==========================================================================
	// clearUserContext
	// ==========================================================================
	describe('clearUserContext', () => {
		it('should set user to null', () => {
			sentryModule.clearUserContext();

			expect(mockSetUser).toHaveBeenCalledWith(null);
		});
	});

	// ==========================================================================
	// captureError
	// ==========================================================================
	describe('captureError', () => {
		it('should call Sentry.captureException with the error', () => {
			const error = new Error('Something went wrong');

			sentryModule.captureError(error);

			expect(mockCaptureException).toHaveBeenCalledWith(error, undefined);
		});

		it('should pass extra context when provided', () => {
			const error = new Error('API error');
			const context = { endpoint: '/api/users', statusCode: 500 };

			sentryModule.captureError(error, context);

			expect(mockCaptureException).toHaveBeenCalledWith(error, {
				extra: { endpoint: '/api/users', statusCode: 500 }
			});
		});

		it('should handle non-Error objects', () => {
			const errorString = 'string error';

			sentryModule.captureError(errorString);

			expect(mockCaptureException).toHaveBeenCalledWith(errorString, undefined);
		});
	});

	// ==========================================================================
	// addBreadcrumb
	// ==========================================================================
	describe('addBreadcrumb', () => {
		it('should add breadcrumb with category and message', () => {
			sentryModule.addBreadcrumb('navigation', 'User navigated to /dashboard');

			expect(mockAddBreadcrumb).toHaveBeenCalledWith({
				category: 'navigation',
				message: 'User navigated to /dashboard',
				level: 'info'
			});
		});

		it('should include data when provided', () => {
			sentryModule.addBreadcrumb('api', 'API call', {
				url: '/api/students',
				method: 'GET'
			});

			expect(mockAddBreadcrumb).toHaveBeenCalledWith({
				category: 'api',
				message: 'API call',
				level: 'info',
				data: { url: '/api/students', method: 'GET' }
			});
		});

		it('should not include data key when data is not provided', () => {
			sentryModule.addBreadcrumb('ui', 'Button clicked');

			const call = mockAddBreadcrumb.mock.calls[0]![0];
			expect(call).not.toHaveProperty('data');
		});
	});
});
