import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page, userEvent } from 'vitest/browser';

vi.mock('$app/navigation', () => ({
	beforeNavigate: () => {},
	afterNavigate: () => {},
	goto: vi.fn(),
	invalidate: vi.fn(),
	invalidateAll: vi.fn(),
	preloadCode: vi.fn(),
	preloadData: vi.fn(),
	pushState: vi.fn(),
	replaceState: vi.fn(),
	disableScrollHandling: vi.fn(),
	onNavigate: () => () => {}
}));

vi.mock('$lib/shared/cookies', () => ({
	csrfFetch: vi.fn()
}));

import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
import { csrfFetch } from '$lib/shared/cookies';
import { goto } from '$app/navigation';
import type { components } from '$lib/generated/api';

const { default: GeschichtePage } = await import('./+page.svelte');

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

type GeschichteView = components['schemas']['GeschichteView'];

const baseGeschichte = (overrides: Partial<GeschichteView> = {}): GeschichteView => ({
	id: 'g1',
	title: 'Die Reise nach Berlin',
	body: '<p>Im Jahr 1923 fuhr Helene...</p>',
	type: 'STORY',
	status: 'PUBLISHED',
	author: { id: 'u1', displayName: 'Anna Schmidt' },
	persons: [],
	items: [],
	createdAt: '2026-01-01T00:00:00Z',
	updatedAt: '2026-01-01T00:00:00Z',
	publishedAt: '2026-04-15T10:00:00Z',
	...overrides
});

const baseData = (overrides: Record<string, unknown> = {}) => ({
	user: undefined,
	canWrite: false,
	canAnnotate: false,
	geschichte: baseGeschichte(),
	canBlogWrite: false,
	...overrides
});

describe('geschichten/[id] page', () => {
	it('renders the geschichte title as the level-1 heading', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect
			.element(page.getByRole('heading', { level: 1, name: /reise nach berlin/i }))
			.toBeVisible();
	});

	it('spans the directory width with a centered reading column inside the sheet (#799)', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		const outer = document.querySelector('[class*="mx-auto"]');
		expect(outer!.className).toContain('max-w-7xl');

		const column = document.querySelector('article [class*="max-w-3xl"]');
		expect(column).not.toBeNull();
		expect(column!.className).toContain('mx-auto');
	});

	it('renders the article on a reading-sheet surface card (#797)', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		const article = document.querySelector('article');
		expect(article).not.toBeNull();
		// bg-sheet sits between the sand canvas and the white cards inside the article
		for (const cls of ['bg-sheet', 'border-line', 'rounded-sm', 'shadow-sm']) {
			expect(article!.className).toContain(cls);
		}
	});

	it('journey badge uses mode-aware journey tokens, not raw orange utilities (#801)', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ geschichte: baseGeschichte({ type: 'JOURNEY' }) }) }
		});

		const badge = document.querySelector('h1')!.parentElement!.querySelector('span');
		expect(badge!.className).toContain('bg-journey-tint');
		expect(badge!.className).toContain('text-journey');
		expect(badge!.className).not.toContain('bg-orange-50');
	});

	it('renders the author full name from firstName + lastName', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect.element(page.getByText(/Anna Schmidt/)).toBeVisible();
	});

	it('renders the server-computed author displayName verbatim', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: {
				data: baseData({
					geschichte: baseGeschichte({ author: { id: 'u2', displayName: 'fallback@example.com' } })
				})
			}
		});

		await expect.element(page.getByText(/fallback@example.com/)).toBeVisible();
	});

	it('renders an empty author when author is absent', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ geschichte: baseGeschichte({ author: undefined }) }) }
		});

		await expect.element(page.getByRole('heading', { level: 1 })).toBeVisible();
	});

	it('renders the publishedAt date suffix when publishedAt is set', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect.element(page.getByText(/veröffentlicht am/i)).toBeVisible();
	});

	it('omits the publishedAt suffix when publishedAt is null', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: {
				data: baseData({ geschichte: baseGeschichte({ publishedAt: undefined }) })
			}
		});

		await expect.element(page.getByText(/veröffentlicht am/i)).not.toBeInTheDocument();
	});

	it('omits the persons section when there are no linked persons', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect.element(page.getByText(/Personen in dieser Geschichte/i)).not.toBeInTheDocument();
	});

	it('renders the persons section when there are linked persons', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: {
				data: baseData({
					geschichte: baseGeschichte({
						persons: [
							{ id: 'p1', firstName: 'Helene', lastName: 'Schmidt' },
							{ id: 'p2', firstName: 'Karl', lastName: 'Müller' }
						]
					})
				})
			}
		});

		await expect.element(page.getByText('Personen in dieser Geschichte')).toBeVisible();
		await expect.element(page.getByText('Helene Schmidt')).toBeVisible();
		await expect.element(page.getByText('Karl Müller')).toBeVisible();
	});

	it('omits the documents section when there are no linked documents', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect.element(page.getByText('Erwähnte Dokumente')).not.toBeInTheDocument();
	});

	it('renders the documents section when there are linked journey items', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: {
				data: baseData({
					geschichte: baseGeschichte({
						items: [
							{
								id: 'item1',
								position: 0,
								document: { id: 'd1', title: 'Brief 1923', datePrecision: 'DAY', receiverCount: 0 },
								note: 'Brief aus 1923'
							}
						]
					})
				})
			}
		});

		await expect.element(page.getByText('Erwähnte Dokumente')).toBeVisible();
		await expect.element(page.getByText('Brief 1923')).toBeVisible();
		await expect.element(page.getByText('Brief aus 1923')).toBeVisible();
		expect(document.querySelector('a[href="/documents/d1"]')).not.toBeNull();
	});

	it('JOURNEY shows "zusammengestellt am" instead of "veröffentlicht am"', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ geschichte: baseGeschichte({ type: 'JOURNEY' }) }) }
		});

		await expect.element(page.getByText(/zusammengestellt am/i)).toBeVisible();
		await expect.element(page.getByText(/veröffentlicht am/i)).not.toBeInTheDocument();
	});

	it('renders the author avatar initials in the meta bar', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData() }
		});

		await expect.element(page.getByText('AS', { exact: true })).toBeInTheDocument();
	});

	it('renders edit and delete actions when canBlogWrite is true', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ canBlogWrite: true }) }
		});

		await expect
			.element(page.getByRole('link', { name: /bearbeiten/i }))
			.toHaveAttribute('href', '/geschichten/g1/edit');
		await expect.element(page.getByRole('button', { name: /löschen/i })).toBeVisible();
	});

	it('hides edit and delete actions when canBlogWrite is false', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ canBlogWrite: false }) }
		});

		await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument();
		await expect.element(page.getByRole('button', { name: /löschen/i })).not.toBeInTheDocument();
	});

	it('STORY with items:[] renders rich-text body and no empty-state message', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: { data: baseData({ geschichte: baseGeschichte({ type: 'STORY', items: [] }) }) }
		});

		await expect.element(page.getByText(/Im Jahr 1923/)).toBeVisible();
		await expect.element(page.getByText(/Diese Lesereise ist noch leer/)).not.toBeInTheDocument();
	});

	it('type:undefined + non-empty body renders StoryReader and no empty-state', async () => {
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, createConfirmService()]]),
			props: {
				data: baseData({
					geschichte: baseGeschichte({
						type: undefined as unknown as 'STORY' | 'JOURNEY',
						items: []
					})
				})
			}
		});

		await expect.element(page.getByText(/Im Jahr 1923/)).toBeVisible();
		await expect.element(page.getByText(/Diese Lesereise ist noch leer/)).not.toBeInTheDocument();
	});

	it('delete success: navigates to /geschichten after confirmed DELETE returns ok', async () => {
		vi.mocked(csrfFetch).mockResolvedValue(new Response(null, { status: 200 }));
		const confirmService = createConfirmService();
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, confirmService]]),
			props: { data: baseData({ canBlogWrite: true }) }
		});

		// Trigger delete — opens confirm dialog
		const deleteBtn = page.getByRole('button', { name: /löschen/i });
		await userEvent.click(deleteBtn);

		// Settle the confirmation dialog
		confirmService.settle(true);

		// Wait for the async delete to complete, then check goto was called
		await vi.waitFor(() => {
			expect(vi.mocked(goto)).toHaveBeenCalledWith('/geschichten');
		});
	});

	it('delete failure: shows error message when DELETE returns non-ok', async () => {
		vi.mocked(csrfFetch).mockResolvedValue(
			new Response(JSON.stringify({ code: 'FORBIDDEN' }), {
				status: 403,
				headers: { 'Content-Type': 'application/json' }
			})
		);
		const confirmService = createConfirmService();
		render(GeschichtePage, {
			context: new Map([[CONFIRM_KEY, confirmService]]),
			props: { data: baseData({ canBlogWrite: true }) }
		});

		// Trigger delete — opens confirm dialog
		const deleteBtn = page.getByRole('button', { name: /löschen/i });
		await userEvent.click(deleteBtn);

		// Settle the confirmation dialog
		confirmService.settle(true);

		// Wait for the error to appear inline
		await expect.element(page.getByRole('alert')).toBeVisible();
		expect(vi.mocked(goto)).not.toHaveBeenCalled();
	});
});
