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

vi.mock('@sentry/sveltekit', () => ({
	init: vi.fn(),
	handleErrorWithSentry: (fn: (args: unknown) => unknown) => fn,
	lastEventId: vi.fn(() => 'sentry-event-id-abc123')
}));

class RedirectMarker {
	constructor(
		public status: number,
		public location: string
	) {}
}

vi.mock('@sveltejs/kit', () => ({
	redirect: vi.fn((status: number, location: string) => new RedirectMarker(status, location)),
	isRedirect: (e: unknown) => e instanceof RedirectMarker
}));
vi.mock('@sveltejs/kit/hooks', () => ({ sequence: vi.fn((...fns: unknown[]) => fns[0]) }));
vi.mock('$lib/paraglide/server', () => ({ paraglideMiddleware: vi.fn() }));
vi.mock('$lib/paraglide/runtime', () => ({ cookieName: 'locale', cookieMaxAge: 86400 }));
vi.mock('$lib/shared/server/locale', () => ({ detectLocale: vi.fn(() => 'de') }));
vi.mock('process', () => ({ env: { API_INTERNAL_URL: 'http://backend:8080' } }));

const makeEvent = () => ({
	url: { pathname: '/documents/123' },
	locals: {}
});

describe('hooks.server handleError', () => {
	beforeEach(() => {
		vi.resetModules();
	});

	it('returns Sentry lastEventId as errorId', async () => {
		const Sentry = await import('@sentry/sveltekit');
		vi.mocked(Sentry.lastEventId).mockReturnValue('sentry-event-id-abc123');

		const { handleError } = await import('./hooks.server');
		const result = (handleError as (args: unknown) => { message: string; errorId: string })({
			error: new Error('boom'),
			event: makeEvent(),
			status: 500,
			message: 'Internal Error'
		});

		expect(result.errorId).toBe('sentry-event-id-abc123');
		expect(result.message).toBe('An unexpected error occurred');
	});

	it('falls back to crypto.randomUUID when lastEventId returns undefined', async () => {
		const Sentry = await import('@sentry/sveltekit');
		vi.mocked(Sentry.lastEventId).mockReturnValue(undefined);

		const { handleError } = await import('./hooks.server');
		const result = (handleError as (args: unknown) => { message: string; errorId: string })({
			error: new Error('boom'),
			event: makeEvent(),
			status: 500,
			message: 'Internal Error'
		});

		expect(result.errorId).toMatch(
			/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
		);
		expect(result.message).toBe('An unexpected error occurred');
	});
});

interface UserGroupEvent {
	url: URL;
	cookies: { get: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> };
	locals: { user?: unknown };
	request: Request;
}

function makeUserGroupEvent(pathname: string, sessionId?: string): UserGroupEvent {
	return {
		url: new URL(`http://localhost${pathname}`),
		cookies: {
			get: vi.fn((name: string) => (name === 'fa_session' ? sessionId : undefined)),
			delete: vi.fn()
		},
		locals: {},
		request: new Request(`http://localhost${pathname}`)
	};
}

describe('hooks.server userGroup (session lookup + 401 handling)', () => {
	beforeEach(() => {
		vi.resetModules();
		vi.stubGlobal('fetch', vi.fn());
	});

	it('redirects to /login?reason=expired when backend rejects the session on a non-public path', async () => {
		vi.mocked(fetch).mockResolvedValue(new Response(null, { status: 401 }));

		const { handle } = await import('./hooks.server');
		const event = makeUserGroupEvent('/documents/123', 'stale-session');
		const resolve = vi.fn();

		await expect((handle as (a: unknown) => unknown)({ event, resolve })).rejects.toMatchObject({
			status: 302,
			location: '/login?reason=expired'
		});

		expect(event.cookies.delete).toHaveBeenCalledWith('fa_session', { path: '/' });
		expect(resolve).not.toHaveBeenCalled();
	});

	it('does not redirect when backend 401 fires on a public path (no /login → /login loop)', async () => {
		vi.mocked(fetch).mockResolvedValue(new Response(null, { status: 401 }));

		const { handle } = await import('./hooks.server');
		const event = makeUserGroupEvent('/login', 'stale-session');
		const resolve = vi.fn().mockResolvedValue(new Response());

		await (handle as (a: unknown) => Promise<unknown>)({ event, resolve });

		expect(event.cookies.delete).toHaveBeenCalledWith('fa_session', { path: '/' });
		expect(resolve).toHaveBeenCalledWith(event);
	});

	it('passes through when no fa_session cookie is present', async () => {
		const { handle } = await import('./hooks.server');
		const event = makeUserGroupEvent('/documents/123', undefined);
		const resolve = vi.fn().mockResolvedValue(new Response());

		await (handle as (a: unknown) => Promise<unknown>)({ event, resolve });

		expect(fetch).not.toHaveBeenCalled();
		expect(resolve).toHaveBeenCalledWith(event);
	});

	it('attaches the user to locals when backend returns 200', async () => {
		vi.mocked(fetch).mockResolvedValue(
			new Response(JSON.stringify({ id: 'u1', email: 'a@b.de' }), {
				status: 200,
				headers: { 'Content-Type': 'application/json' }
			})
		);

		const { handle } = await import('./hooks.server');
		const event = makeUserGroupEvent('/documents/123', 'valid-session');
		const resolve = vi.fn().mockResolvedValue(new Response());

		await (handle as (a: unknown) => Promise<unknown>)({ event, resolve });

		expect((event.locals as { user: { email: string } }).user.email).toBe('a@b.de');
	});
});
