import { beforeEach, describe, expect, it, vi } from 'vitest';
import { load, actions } from './+page.server';

const mockApi = {
	GET: vi.fn(),
	PATCH: vi.fn(),
	POST: vi.fn()
};

vi.mock('$lib/shared/api.server', () => ({
	createApiClient: () => mockApi,
	extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code
}));

function buildUrl(search = ''): URL {
	return new URL(`http://localhost/aktivitaeten${search}`);
}

function mockSuccess() {
	mockApi.GET.mockImplementation((path: string) => {
		if (path === '/api/dashboard/activity') {
			return Promise.resolve({ response: { ok: true }, data: [] });
		}
		return Promise.resolve({ response: { ok: true }, data: { content: [] } });
	});
}

beforeEach(() => {
	vi.clearAllMocks();
});

describe('aktivitaeten/load — core', () => {
	it('requests only unread notifications for Für-dich', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl()
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/notifications', {
			params: { query: { read: false, page: 0, size: 20 } }
		});
	});

	it('returns the activity feed and unread notifications on success', async () => {
		const feed = [{ kind: 'FILE_UPLOADED', documentId: 'd1' }];
		const unread = [{ id: 'n1', type: 'MENTION' }];
		mockApi.GET.mockImplementation((path: string) => {
			if (path === '/api/dashboard/activity') {
				return Promise.resolve({ response: { ok: true }, data: feed });
			}
			return Promise.resolve({ response: { ok: true }, data: { content: unread } });
		});

		const result = await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl()
		} as never);

		expect(result.activityFeed).toEqual(feed);
		expect(result.unreadNotifications).toEqual(unread);
		expect(result.filter).toBe('alle');
		expect(result.loadError).toBeNull();
	});

	it('surfaces "activity" loadError when the dashboard endpoint returns non-ok', async () => {
		mockApi.GET.mockImplementation((path: string) => {
			if (path === '/api/dashboard/activity') {
				return Promise.resolve({ response: { ok: false, status: 500 }, error: {} });
			}
			return Promise.resolve({ response: { ok: true }, data: { content: [] } });
		});

		const result = await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl()
		} as never);

		expect(result.loadError).toBe('activity');
		expect(result.activityFeed).toEqual([]);
	});

	it('parses the filter query param, falling back to "alle" for invalid values', async () => {
		mockApi.GET.mockResolvedValue({ response: { ok: true }, data: [] });
		const validResult = await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=fuer-dich')
		} as never);
		expect(validResult.filter).toBe('fuer-dich');

		mockApi.GET.mockResolvedValue({ response: { ok: true }, data: [] });
		const invalidResult = await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=bogus')
		} as never);
		expect(invalidResult.filter).toBe('alle');
	});
});

describe('aktivitaeten/load — kinds param per filter', () => {
	it('omits kinds for filter=alle (server defaults to ROLLUP_ELIGIBLE)', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl()
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/dashboard/activity', {
			params: { query: { limit: 40 } }
		});
	});

	it('omits kinds for filter=fuer-dich (client-side filtering on youMentioned/youParticipated)', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=fuer-dich')
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/dashboard/activity', {
			params: { query: { limit: 40 } }
		});
	});

	it('sends kinds=FILE_UPLOADED for filter=hochgeladen', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=hochgeladen')
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/dashboard/activity', {
			params: { query: { limit: 40, kinds: ['FILE_UPLOADED'] } }
		});
	});

	it('sends TEXT_SAVED, BLOCK_REVIEWED, ANNOTATION_CREATED for filter=transkription', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=transkription')
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/dashboard/activity', {
			params: {
				query: {
					limit: 40,
					kinds: expect.arrayContaining(['TEXT_SAVED', 'BLOCK_REVIEWED', 'ANNOTATION_CREATED'])
				}
			}
		});
		const call = mockApi.GET.mock.calls.find(([p]) => p === '/api/dashboard/activity');
		expect(call[1].params.query.kinds).toHaveLength(3);
	});

	it('sends COMMENT_ADDED, MENTION_CREATED for filter=kommentare', async () => {
		mockSuccess();
		await load({
			fetch,
			request: new Request('http://localhost/aktivitaeten'),
			url: buildUrl('?filter=kommentare')
		} as never);
		expect(mockApi.GET).toHaveBeenCalledWith('/api/dashboard/activity', {
			params: {
				query: {
					limit: 40,
					kinds: expect.arrayContaining(['COMMENT_ADDED', 'MENTION_CREATED'])
				}
			}
		});
		const call = mockApi.GET.mock.calls.find(([p]) => p === '/api/dashboard/activity');
		expect(call[1].params.query.kinds).toHaveLength(2);
	});
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function makeActionEvent(formData: FormData): any {
	return {
		request: new Request('http://localhost/aktivitaeten', { method: 'POST', body: formData }),
		fetch
	};
}

describe('aktivitaeten/actions — dismiss-notification', () => {
	it('returns fail(400, { error }) and does NOT call PATCH when notificationId is missing', async () => {
		const result = await actions['dismiss-notification'](makeActionEvent(new FormData()));

		expect(result).toMatchObject({ status: 400 });
		expect(mockApi.PATCH).not.toHaveBeenCalled();
	});

	it('calls PATCH /api/notifications/{id}/read with the form-supplied notificationId', async () => {
		mockApi.PATCH.mockResolvedValue({ response: { ok: true }, data: {} });
		const fd = new FormData();
		fd.set('notificationId', 'n-abc');

		await actions['dismiss-notification'](makeActionEvent(fd));

		expect(mockApi.PATCH).toHaveBeenCalledWith('/api/notifications/{id}/read', {
			params: { path: { id: 'n-abc' } }
		});
	});

	it('returns { success: true } when the API responds ok', async () => {
		mockApi.PATCH.mockResolvedValue({ response: { ok: true }, data: {} });
		const fd = new FormData();
		fd.set('notificationId', 'n-abc');

		const result = await actions['dismiss-notification'](makeActionEvent(fd));

		expect(result).toEqual({ success: true });
	});

	it('returns fail(status, { error }) when the API responds non-ok', async () => {
		mockApi.PATCH.mockResolvedValue({
			response: { ok: false, status: 403 },
			error: { code: 'NOTIFICATION_NOT_FOUND' }
		});
		const fd = new FormData();
		fd.set('notificationId', 'n-abc');

		const result = await actions['dismiss-notification'](makeActionEvent(fd));

		expect(result).toMatchObject({ status: 403 });
	});
});

describe('aktivitaeten/actions — mark-all-read', () => {
	it('calls POST /api/notifications/read-all', async () => {
		mockApi.POST.mockResolvedValue({ response: { ok: true }, data: null });

		await actions['mark-all-read'](makeActionEvent(new FormData()));

		expect(mockApi.POST).toHaveBeenCalledWith('/api/notifications/read-all');
	});

	it('returns { success: true } when the API responds ok', async () => {
		mockApi.POST.mockResolvedValue({ response: { ok: true }, data: null });

		const result = await actions['mark-all-read'](makeActionEvent(new FormData()));

		expect(result).toEqual({ success: true });
	});

	it('returns fail(status, { error }) when the API responds non-ok', async () => {
		mockApi.POST.mockResolvedValue({
			response: { ok: false, status: 500 },
			error: { code: 'INTERNAL_ERROR' }
		});

		const result = await actions['mark-all-read'](makeActionEvent(new FormData()));

		expect(result).toMatchObject({ status: 500 });
	});
});
