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

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

import { load } from './+page.server';
import { createApiClient } from '$lib/shared/api.server';

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

function makeUrl(params: Record<string, string> = {}) {
	const url = new URL('http://localhost/persons');
	for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
	return url;
}

/** Invokes the loader with a minimal event; the partial event is cast to satisfy the type. */
function runLoad(url: URL, user: unknown) {
	return load({
		url,
		fetch: vi.fn() as unknown as typeof fetch,
		request: new Request('http://localhost/persons'),
		locals: { user } as App.Locals
	} as unknown as Parameters<typeof load>[0]);
}

/** Mock the typed client. /api/persons returns a paged envelope; /api/stats returns counts. */
function mockApi() {
	const personsResult = {
		response: { ok: true, status: 200 },
		data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
	};
	// Loose `...args` signature (matching the documents loader spec) so call tuples aren't
	// narrowed to length 1 — the test inspects calls[i][1].params.query.
	const get = vi.fn((...args: unknown[]) => {
		if (args[0] === '/api/stats') {
			return Promise.resolve({
				response: { ok: true, status: 200 },
				data: { totalPersons: 7, totalDocuments: 3 }
			});
		}
		return Promise.resolve(personsResult);
	});
	vi.mocked(createApiClient).mockReturnValue({ GET: get } as unknown as ReturnType<
		typeof createApiClient
	>);
	return get;
}

const writer = { groups: [{ permissions: ['READ_ALL', 'WRITE_ALL'] }] };
const reader = { groups: [{ permissions: ['READ_ALL'] }] };

type GetCall = [string, { params: { query: Record<string, unknown> } }];

/** Find the GET call to a path, optionally narrowing by a query predicate. */
function findCall(
	get: ReturnType<typeof vi.fn>,
	path: string,
	matchQuery?: (q: Record<string, unknown>) => boolean
): GetCall | undefined {
	return (get.mock.calls as unknown as GetCall[]).find(
		(c) => c[0] === path && (!matchQuery || matchQuery(c[1].params.query))
	);
}

describe('persons page load — reader default', () => {
	it('does NOT pass review when no review param is present (clean reader default)', async () => {
		const get = mockApi();

		await runLoad(makeUrl(), reader);

		const personsCall = findCall(get, '/api/persons');
		expect(personsCall?.[1].params.query.review).toBeUndefined();
	});

	it('passes review=true when review=1 is in the URL', async () => {
		const get = mockApi();

		await runLoad(makeUrl({ review: '1' }), reader);

		const personsCall = findCall(get, '/api/persons');
		expect(personsCall?.[1].params.query.review).toBe(true);
	});
});

describe('persons page load — filter forwarding', () => {
	it('forwards type, familyOnly, hasDocuments and page to the API', async () => {
		const get = mockApi();

		await runLoad(
			makeUrl({ type: 'INSTITUTION', familyOnly: 'true', hasDocuments: 'true', page: '2' }),
			reader
		);

		const personsCall = findCall(get, '/api/persons');
		expect(personsCall?.[1].params.query).toMatchObject({
			type: 'INSTITUTION',
			familyOnly: true,
			hasDocuments: true,
			page: 2,
			size: 50
		});
	});

	it('clamps a negative page to 0', async () => {
		const get = mockApi();

		await runLoad(makeUrl({ page: '-5' }), reader);

		const personsCall = findCall(get, '/api/persons');
		expect(personsCall?.[1].params.query.page).toBe(0);
	});
});

describe('persons page load — needsReviewCount', () => {
	it('fires a provisional count request for writers', async () => {
		const get = mockApi();

		await runLoad(makeUrl(), writer);

		const provisionalCall = findCall(get, '/api/persons', (query) => query.provisional === true);
		expect(provisionalCall).toBeDefined();
	});

	it('does not fire a provisional count request for read-only users', async () => {
		const get = mockApi();

		const result = await runLoad(makeUrl(), reader);

		const provisionalCall = findCall(get, '/api/persons', (query) => query.provisional === true);
		expect(provisionalCall).toBeUndefined();
		expect(result.needsReviewCount).toBe(0);
		expect(result.canWrite).toBe(false);
	});
});
