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/');
	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;
}

function contributorParent() {
	return vi.fn().mockResolvedValue({ canWrite: true, canAnnotate: false, canBlogWrite: false });
}

// ─── always-dashboard behaviour ───────────────────────────────────────────────

it('never calls /api/documents/search regardless of URL params', async () => {
	const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
	vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
		typeof createApiClient
	>);

	await load({
		url: makeUrl({ q: 'Urlaub', from: '2020-01-01' }),
		request: new Request('http://localhost/'),
		fetch: vi.fn() as unknown as typeof fetch,
		parent: contributorParent()
	} as Parameters<typeof load>[0]);

	const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0]);
	expect(calledEndpoints).not.toContain('/api/documents/search');
});

it('always fetches dashboard data regardless of URL params', async () => {
	const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
	vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
		typeof createApiClient
	>);

	await load({
		url: makeUrl({ q: 'Urlaub' }),
		request: new Request('http://localhost/'),
		fetch: vi.fn() as unknown as typeof fetch,
		parent: contributorParent()
	} as Parameters<typeof load>[0]);

	const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0]);
	expect(calledEndpoints).toContain('/api/stats');
	expect(calledEndpoints).toContain('/api/dashboard/resume');
	expect(calledEndpoints).toContain('/api/dashboard/pulse');
	expect(calledEndpoints).toContain('/api/dashboard/activity');
});

// ─── dashboard mode ────────────────────────────────────────────────────────────

describe('home page load — dashboard', () => {
	it('fetches stats, resume, pulse, and activity APIs', async () => {
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { totalDocuments: 42, totalPersons: 7 }
			}) // stats
			.mockResolvedValueOnce({
				response: { ok: true },
				data: {
					documentId: 'd1',
					title: 'T',
					caption: '',
					excerpt: '',
					totalBlocks: 2,
					pct: 50,
					collaborators: []
				}
			}) // resume
			.mockResolvedValueOnce({
				response: { ok: true },
				data: {
					pages: 5,
					annotated: 1,
					transcribed: 2,
					uploaded: 1,
					yourPages: 3,
					contributors: []
				}
			}) // pulse
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 }
			}) // weekly-stats
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete
			.mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }) // incomplete-count
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // tags/tree
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.stats).toEqual({ totalDocuments: 42, totalPersons: 7 });
		expect(result.resumeDoc).not.toBeNull();
		expect(result.resumeDoc?.totalBlocks).toBe(2);
		expect(result.pulse).not.toBeNull();
		expect(result.activityFeed).toEqual([]);
	});

	it('returns stats with totalDocuments from /api/stats', async () => {
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { totalDocuments: 248, totalPersons: 34 }
			}) // stats
			.mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume
			.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 }
			}) // weekly-stats
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete
			.mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }) // incomplete-count
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // tags/tree
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.stats?.totalDocuments).toBe(248);
		expect(result.stats?.totalPersons).toBe(34);
	});

	it('returns stats: null when /api/stats rejects', async () => {
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
			.mockRejectedValueOnce(new Error('network')) // stats
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // resume
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // pulse
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.stats).toBeNull();
	});

	it('defaults resumeDoc to null when resume API rejects', async () => {
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
			.mockResolvedValueOnce({ response: { ok: true }, data: { content: [] } }) // stats
			.mockRejectedValueOnce(new Error('network')) // resume
			.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // activity
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.resumeDoc).toBeNull();
	});

	it('defaults activityFeed to [] when activity API rejects', async () => {
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { totalDocuments: 0, totalPersons: 0 }
			}) // stats
			.mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume
			.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
			.mockRejectedValueOnce(new Error('network')); // activity
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.activityFeed).toEqual([]);
	});
});

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

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

		await expect(
			load({
				url: makeUrl(),
				request: new Request('http://localhost/'),
				fetch: vi.fn() as unknown as typeof fetch,
				parent: contributorParent()
			} as Parameters<typeof load>[0])
		).rejects.toMatchObject({ location: '/login' });
	});
});

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

describe('home 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/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: contributorParent()
		} as Parameters<typeof load>[0]);

		expect(result.error).toBe('Daten konnten nicht geladen werden.');
	});
});

// ─── reader branch ─────────────────────────────────────────────────────────────

describe('home page load — reader branch (isReader = !canWrite && !canAnnotate)', () => {
	it('does not call /api/transcription/* endpoints for a read-only user', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0] as string);
		const transcriptionCalls = calledEndpoints.filter((ep: string) =>
			ep.startsWith('/api/transcription')
		);
		expect(transcriptionCalls).toHaveLength(0);
	});

	it('calls /api/stats, /api/persons, /api/documents/search, /api/geschichten for a read-only user', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0] as string);
		expect(calledEndpoints).toContain('/api/stats');
		expect(calledEndpoints).toContain('/api/persons');
		expect(calledEndpoints).toContain('/api/documents/search');
		expect(calledEndpoints).toContain('/api/geschichten');
	});

	it('does not call /api/geschichten with status=DRAFT when canBlogWrite is false', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		const draftCalls = mockGet.mock.calls.filter(
			(c: unknown[]) =>
				c[0] === '/api/geschichten' &&
				(c[1] as { params?: { query?: { status?: string } } })?.params?.query?.status === 'DRAFT'
		);
		expect(draftCalls).toHaveLength(0);
	});

	it('calls /api/geschichten with status=DRAFT when canBlogWrite is true', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi.fn().mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: true })
		} as Parameters<typeof load>[0]);

		const draftCalls = mockGet.mock.calls.filter(
			(c: unknown[]) =>
				c[0] === '/api/geschichten' &&
				(c[1] as { params?: { query?: { status?: string } } })?.params?.query?.status === 'DRAFT'
		);
		expect(draftCalls).toHaveLength(1);
	});

	it('returns isReader: true for read-only user', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

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

	it('returns isReader: false for contributor with WRITE_ALL', async () => {
		const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi.fn().mockResolvedValue({ canWrite: true, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		expect(result.isReader).toBe(false);
	});

	it('maps search result items directly to recentDocs without wrapping in a .document property', async () => {
		const searchItem = {
			id: 'd1',
			title: 'Liebesbrief',
			originalFilename: 'letter.pdf',
			completionPercentage: 80,
			receivers: [],
			tags: [],
			contributors: [],
			matchData: { titleOffsets: [], senderMatched: false },
			createdAt: '2026-05-01T10:00:00Z',
			updatedAt: '2026-05-10T08:00:00Z'
		};
		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // initial persons
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { totalDocuments: 1, totalPersons: 1 }
			}) // stats
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // topPersons
			.mockResolvedValueOnce({
				response: { ok: true },
				data: { items: [searchItem], totalElements: 1, pageNumber: 0, pageSize: 5, totalPages: 1 }
			}) // search
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // stories
			.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // tags/tree
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		expect(result.isReader).toBe(true);
		if (result.isReader) {
			expect(result.recentDocs).toHaveLength(1);
			expect(result.recentDocs[0]).toBeDefined();
			expect(result.recentDocs[0].id).toBe('d1');
			expect(result.recentDocs[0].createdAt).toBe('2026-05-01T10:00:00Z');
			expect(result.recentDocs[0].updatedAt).toBe('2026-05-10T08:00:00Z');
		}
	});

	it('returns topPersons=[] when topPersons fetch fails, rest of data still loads', async () => {
		const okStats = {
			response: { ok: true, status: 200 },
			data: { totalDocuments: 5, totalPersons: 2, totalStories: 1 }
		};
		const failPersons = Promise.reject(new Error('timeout'));
		const okSearch = { response: { ok: true, status: 200 }, data: { items: [] } };
		const okStories = { response: { ok: true, status: 200 }, data: [] };

		const mockGet = vi
			.fn()
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // initial persons check
			.mockResolvedValueOnce(okStats)
			.mockReturnValueOnce(failPersons)
			.mockResolvedValueOnce(okSearch)
			.mockResolvedValueOnce(okStories)
			.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); // tags/tree
		vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
			typeof createApiClient
		>);

		const result = await load({
			url: makeUrl(),
			request: new Request('http://localhost/'),
			fetch: vi.fn() as unknown as typeof fetch,
			parent: vi
				.fn()
				.mockResolvedValue({ canWrite: false, canAnnotate: false, canBlogWrite: false })
		} as Parameters<typeof load>[0]);

		expect(result.isReader).toBe(true);
		if (result.isReader) {
			expect(result.topPersons).toEqual([]);
			expect(result.readerStats?.totalDocuments).toBe(5);
		}
	});
});
