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 | string[]> = {}) {
	const url = new URL('http://localhost/documents');
	for (const [key, value] of Object.entries(params)) {
		if (Array.isArray(value)) {
			value.forEach((v) => url.searchParams.append(key, v));
		} else {
			url.searchParams.set(key, value);
		}
	}
	return url;
}

// ─── search params forwarding ─────────────────────────────────────────────────

describe('documents page load — search params', () => {
	it('passes q, from, to to the search API', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(mockGet).toHaveBeenCalledWith(
			'/api/documents/search',
			expect.objectContaining({
				params: expect.objectContaining({
					query: expect.objectContaining({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' })
				})
			})
		);
	});

	it('passes senderId and receiverId to the search API', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl({ senderId: 'p-1', receiverId: 'p-2' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(mockGet).toHaveBeenCalledWith(
			'/api/documents/search',
			expect.objectContaining({
				params: expect.objectContaining({
					query: expect.objectContaining({ senderId: 'p-1', receiverId: 'p-2' })
				})
			})
		);
	});

	it('passes sort, dir, tagQ to the search API', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(mockGet).toHaveBeenCalledWith(
			'/api/documents/search',
			expect.objectContaining({
				params: expect.objectContaining({
					query: expect.objectContaining({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' })
				})
			})
		);
	});

	it('forwards undated=true to the search API as a boolean true', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl({ undated: 'true' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(mockGet).toHaveBeenCalledWith(
			'/api/documents/search',
			expect.objectContaining({
				params: expect.objectContaining({
					query: expect.objectContaining({ undated: true })
				})
			})
		);
	});

	it('omits undated from the query when the param is absent', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl({}),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		const query = mockGet.mock.calls[0][1].params.query;
		expect(query.undated).toBeUndefined();
	});

	it('treats any undated value other than the literal "true" as not-undated', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl({ undated: '1' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.undated).toBe(false);
		expect(mockGet.mock.calls[0][1].params.query.undated).toBeUndefined();
	});

	it('returns the undated flag in page data when enabled', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl({ undated: 'true' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.undated).toBe(true);
	});

	it('does not carry page when toggling undated (page reset)', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		// A bare undated toggle URL carries no page param → loader requests page 0.
		await load({
			url: makeUrl({ undated: 'true' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(mockGet.mock.calls[0][1].params.query.page).toBe(0);
	});

	it('returns items and total from the search result', async () => {
		const item = {
			document: { id: 'd1' },
			matchData: {},
			completionPercentage: 0,
			contributors: []
		};
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [item], totalElements: 42, pageNumber: 0, pageSize: 50, totalPages: 1 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl({ q: 'test' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.items).toHaveLength(1);
		expect(result.totalElements).toBe(42);
	});

	it('forwards the global undatedCount from the search result (#668)', async () => {
		// The backend returns the global undated total for the active filter across
		// ALL pages; the loader must pass it straight through, not recompute it locally.
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: {
				items: [],
				totalElements: 200,
				pageNumber: 0,
				pageSize: 50,
				totalPages: 4,
				undatedCount: 73
			}
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl({ q: 'test' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.undatedCount).toBe(73);
	});

	it('defaults undatedCount to 0 when the search result omits it', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.undatedCount).toBe(0);
	});

	it('returns filter values in the result for pre-filling the UI', async () => {
		const mockGet = vi.fn().mockResolvedValue({
			response: { ok: true, status: 200 },
			data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl({ q: 'Urlaub', from: '1920-01-01', sort: 'TITLE', dir: 'asc' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.q).toBe('Urlaub');
		expect(result.from).toBe('1920-01-01');
		expect(result.sort).toBe('TITLE');
		expect(result.dir).toBe('asc');
	});
});

// ─── 401 redirect ─────────────────────────────────────────────────────────────

describe('documents page load — auth redirect', () => {
	it('redirects to /login when search API returns 401', async () => {
		vi.mocked(createApiClient).mockReturnValue({
			GET: vi.fn().mockResolvedValue({ response: { ok: false, status: 401 }, data: null })
		} as ReturnType<typeof createApiClient>);

		await expect(
			load({
				url: makeUrl(),
				request: new Request('http://localhost/documents'),
				fetch: vi.fn() as unknown as typeof fetch
			})
		).rejects.toMatchObject({ location: '/login' });
	});
});

// ─── network error fallback ───────────────────────────────────────────────────

describe('documents page load — network error fallback', () => {
	it('returns error string instead of throwing when API call throws', async () => {
		vi.mocked(createApiClient).mockReturnValue({
			GET: vi.fn().mockRejectedValue(new Error('Network failure'))
		} as ReturnType<typeof createApiClient>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.error).toBeTruthy();
		expect(result.items).toEqual([]);
	});
});

// ─── person name resolution ───────────────────────────────────────────────────

describe('documents page load — person name resolution', () => {
	function makeSearchMock(personResult?: { ok: boolean; displayName?: string }) {
		const mockGet = vi.fn().mockImplementation((path: string) => {
			if (path === '/api/documents/search') {
				return Promise.resolve({
					response: { ok: true, status: 200 },
					data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
				});
			}
			// person lookup via api.GET('/api/persons/{id}', ...)
			if (!personResult?.ok) {
				return Promise.resolve({ response: { ok: false, status: 404 }, data: undefined });
			}
			return Promise.resolve({
				response: { ok: true, status: 200 },
				data: { displayName: personResult.displayName ?? '' }
			});
		});
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);
		return mockGet;
	}

	it('returns initialSenderName from person lookup when senderId is a valid UUID', async () => {
		makeSearchMock({ ok: true, displayName: 'Max Mustermann' });

		const result = await load({
			url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.initialSenderName).toBe('Max Mustermann');
	});

	it('returns initialReceiverName from person lookup when receiverId is a valid UUID', async () => {
		makeSearchMock({ ok: true, displayName: 'Anna Musterfrau' });

		const result = await load({
			url: makeUrl({ receiverId: '22222222-2222-2222-2222-222222222222' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.initialReceiverName).toBe('Anna Musterfrau');
	});

	it('returns empty string when senderId is not a valid UUID', async () => {
		const mockGet = makeSearchMock();

		const result = await load({
			url: makeUrl({ senderId: 'not-a-uuid' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.initialSenderName).toBe('');
		// UUID guard fires before any api.GET call — only document search is called
		expect(mockGet).toHaveBeenCalledTimes(1);
	});

	it('returns empty string when person api returns 404', async () => {
		makeSearchMock({ ok: false });

		const result = await load({
			url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }),
			request: new Request('http://localhost/documents'),
			fetch: vi.fn() as unknown as typeof fetch
		});

		expect(result.initialSenderName).toBe('');
	});
});
