import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import { goto } from '$app/navigation';
import DocumentRow from './DocumentRow.svelte';
import { bulkSelectionStore } from '$lib/document/bulkSelection.svelte';
import type { components } from '$lib/generated/api';

vi.mock('$app/navigation', () => ({ goto: vi.fn() }));

afterEach(() => {
	cleanup();
	vi.mocked(goto).mockClear();
	bulkSelectionStore.clear();
});

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

function makeItem(overrides: Partial<DocumentListItem> = {}): DocumentListItem {
	return {
		id: '1',
		title: 'Testbrief',
		originalFilename: 'testbrief.pdf',
		documentDate: '2024-03-15',
		metaDatePrecision: 'DAY',
		sender: undefined,
		receivers: [],
		tags: [],
		matchData: {
			titleOffsets: [],
			senderMatched: false,
			matchedReceiverIds: [],
			matchedTagIds: [],
			snippetOffsets: [],
			summaryOffsets: []
		},
		completionPercentage: 0,
		contributors: [],
		...overrides
	};
}

// ─── Title ────────────────────────────────────────────────────────────────────

describe('DocumentRow – title', () => {
	it('renders document title', async () => {
		render(DocumentRow, { item: makeItem() });
		await expect.element(page.getByRole('heading', { name: 'Testbrief' })).toBeInTheDocument();
	});

	it('falls back to originalFilename when title is null', async () => {
		const item = makeItem({ title: null as unknown as string });
		render(DocumentRow, { item });
		await expect.element(page.getByRole('heading', { name: 'testbrief.pdf' })).toBeInTheDocument();
	});

	it('renders a mark element for highlighted title offsets', async () => {
		const item = makeItem({
			title: 'Brief an Anna',
			matchData: {
				titleOffsets: [{ start: 0, length: 5 }],
				senderMatched: false,
				matchedReceiverIds: [],
				matchedTagIds: [],
				snippetOffsets: [],
				summaryOffsets: []
			}
		});
		render(DocumentRow, { item });
		const mark = page.getByRole('mark');
		await expect.element(mark).toBeInTheDocument();
		await expect.element(mark).toHaveTextContent('Brief');
	});
});

// ─── Date rendering (#668) ──────────────────────────────────────────────────

describe('DocumentRow – date rendering', () => {
	it('renders a "Datum unbekannt" badge for an undated document', async () => {
		const item = makeItem({ documentDate: undefined, metaDatePrecision: 'UNKNOWN' });
		render(DocumentRow, { item });
		// The badge text appears (once per breakpoint block).
		await expect.element(page.getByText('Datum unbekannt').first()).toBeInTheDocument();
	});

	it('does not render a bare em-dash for an undated document', async () => {
		const item = makeItem({ documentDate: undefined, metaDatePrecision: 'UNKNOWN' });
		render(DocumentRow, { item });
		await expect.element(page.getByText('—', { exact: true }).first()).not.toBeInTheDocument();
	});

	it('renders the full date for a day-precision document', async () => {
		const item = makeItem({ documentDate: '1943-12-24', metaDatePrecision: 'DAY' });
		render(DocumentRow, { item });
		await expect.element(page.getByText(/24\. Dezember 1943/).first()).toBeInTheDocument();
	});

	it('renders month precision honestly without fabricating a day', async () => {
		const item = makeItem({ documentDate: '1916-06-01', metaDatePrecision: 'MONTH' });
		render(DocumentRow, { item });
		await expect.element(page.getByText(/Juni 1916/).first()).toBeInTheDocument();
	});
});

// ─── Snippet ──────────────────────────────────────────────────────────────────

describe('DocumentRow – snippet', () => {
	it('shows transcription snippet when present', async () => {
		const item = makeItem({
			matchData: {
				transcriptionSnippet: 'Er schrieb einen langen Brief',
				titleOffsets: [],
				senderMatched: false,
				matchedReceiverIds: [],
				matchedTagIds: [],
				snippetOffsets: [],
				summaryOffsets: []
			}
		});
		render(DocumentRow, { item });
		await expect.element(page.getByText('Er schrieb einen langen Brief')).toBeInTheDocument();
	});

	it('does not render snippet section when no snippet', async () => {
		render(DocumentRow, { item: makeItem() });
		await expect.element(page.getByTestId('search-snippet')).not.toBeInTheDocument();
	});
});

// ─── Sender / receivers ───────────────────────────────────────────────────────

describe('DocumentRow – sender', () => {
	it('shows sender display name', async () => {
		const item = makeItem({
			sender: {
				id: 's1',
				lastName: 'Maria',
				displayName: 'Großmutter Maria',
				personType: 'PERSON',
				familyMember: false
			}
		});
		render(DocumentRow, { item });
		await expect.element(page.getByText('Großmutter Maria').first()).toBeInTheDocument();
	});

	it('shows unknown fallback when sender is null', async () => {
		render(DocumentRow, { item: makeItem() });
		const unknownElements = page.getByText('Unbekannt');
		await expect.element(unknownElements.first()).toBeInTheDocument();
	});

	it('highlights the sender when senderMatched is true', async () => {
		const item = makeItem({
			sender: {
				id: 's1',
				lastName: 'Maria',
				displayName: 'Großmutter Maria',
				personType: 'PERSON',
				familyMember: false
			},
			matchData: {
				...makeItem().matchData,
				senderMatched: true
			}
		});
		render(DocumentRow, { item });
		const mark = page.getByRole('mark').first();
		await expect.element(mark).toHaveTextContent('Großmutter Maria');
	});

	it('highlights a receiver when matchedReceiverIds includes its id', async () => {
		const item = makeItem({
			receivers: [
				{
					id: 'r1',
					lastName: 'Karl',
					displayName: 'Onkel Karl',
					personType: 'PERSON',
					familyMember: false
				}
			],
			matchData: {
				...makeItem().matchData,
				matchedReceiverIds: ['r1']
			}
		});
		render(DocumentRow, { item });
		const mark = page.getByRole('mark').first();
		await expect.element(mark).toHaveTextContent('Onkel Karl');
	});
});

// ─── Summary ─────────────────────────────────────────────────────────────────

describe('DocumentRow – summary', () => {
	it('renders the document summary when present', async () => {
		const item = makeItem({
			summary: 'Brief von Eugenie über die Heimreise aus dem Süden.'
		});
		render(DocumentRow, { item });
		await expect
			.element(page.getByTestId('doc-summary'))
			.toHaveTextContent('Brief von Eugenie über die Heimreise aus dem Süden.');
	});

	it('does not render the summary block when summary is empty', async () => {
		render(DocumentRow, { item: makeItem() });
		await expect.element(page.getByTestId('doc-summary')).not.toBeInTheDocument();
	});

	it('applies summary search-match highlight via summaryOffsets', async () => {
		const item = makeItem({
			summary: 'Brief über Menton',
			matchData: {
				...makeItem().matchData,
				summaryOffsets: [{ start: 11, length: 6 }]
			}
		});
		render(DocumentRow, { item });
		const mark = page.getByRole('mark').first();
		await expect.element(mark).toHaveTextContent('Menton');
	});
});

// ─── Archive chips ───────────────────────────────────────────────────────────

describe('DocumentRow – archive chips', () => {
	it('renders the archive box chip when set', async () => {
		const item = makeItem({ archiveBox: 'K3' });
		render(DocumentRow, { item });
		await expect.element(page.getByText('K3')).toBeInTheDocument();
	});

	it('renders the archive folder chip when set', async () => {
		const item = makeItem({ archiveFolder: 'Mappe A' });
		render(DocumentRow, { item });
		await expect.element(page.getByText('Mappe A')).toBeInTheDocument();
	});

	it('renders the location chip when meta_location is set', async () => {
		const item = makeItem({ location: 'Berlin' });
		render(DocumentRow, { item });
		await expect.element(page.getByText('Berlin')).toBeInTheDocument();
	});
});

// ─── Tags ─────────────────────────────────────────────────────────────────────

describe('DocumentRow – tags', () => {
	it('renders tag buttons', async () => {
		const item = makeItem({
			tags: [{ id: 't1', name: 'Familie' }]
		});
		render(DocumentRow, { item });
		await expect.element(page.getByRole('button', { name: 'Familie' })).toBeInTheDocument();
	});

	it('navigates to /documents?tag=… on tag click', async () => {
		const item = makeItem({
			tags: [{ id: 't1', name: 'Urlaub & Reise' }]
		});
		render(DocumentRow, { item });
		// Tailwind CSS isn't loaded in the vitest-browser client project, so the
		// `z-10` that elevates the content wrapper above the stretched-link
		// overlay anchor has no effect here — Playwright's coordinate-based
		// click would hit the anchor instead of the tag button. Fire the click
		// directly on the button to verify the handler logic.
		document.querySelector<HTMLButtonElement>('button')?.click();
		await expect
			.poll(() => vi.mocked(goto).mock.calls[0]?.[0])
			.toBe('/documents?tag=Urlaub%20%26%20Reise');
	});

	it('tag click does not navigate to the document detail page', async () => {
		const item = makeItem({
			tags: [{ id: 't2', name: 'Familie' }]
		});
		render(DocumentRow, { item });
		const before = window.location.href;
		await page.getByRole('button', { name: 'Familie' }).click();
		expect(window.location.href).toBe(before);
	});
});

// ─── Bulk-selection checkbox ─────────────────────────────────────────────────

describe('DocumentRow – bulk selection checkbox', () => {
	it('does not render the checkbox when canWrite is false', async () => {
		render(DocumentRow, { item: makeItem(), canWrite: false });
		await expect.element(page.getByTestId('bulk-select-checkbox')).not.toBeInTheDocument();
	});

	it('renders the checkbox when canWrite is true', async () => {
		render(DocumentRow, { item: makeItem(), canWrite: true });
		await expect.element(page.getByTestId('bulk-select-checkbox')).toBeInTheDocument();
	});

	it('checkbox aria-label includes the document title', async () => {
		const item = makeItem({ title: 'Brief an Anna' });
		render(DocumentRow, { item, canWrite: true });
		await expect
			.element(page.getByRole('checkbox', { name: /Brief an Anna/i }))
			.toBeInTheDocument();
	});

	it('toggling the checkbox calls bulkSelectionStore.toggle', async () => {
		const item = makeItem({ id: 'doc-42' });
		render(DocumentRow, { item, canWrite: true });
		expect(bulkSelectionStore.has('doc-42')).toBe(false);

		document.querySelector<HTMLInputElement>('input[type="checkbox"]')?.click();

		await expect.poll(() => bulkSelectionStore.has('doc-42')).toBe(true);
	});

	it('checked state mirrors the store', async () => {
		bulkSelectionStore.add('doc-99');
		const item = makeItem({ id: 'doc-99' });
		render(DocumentRow, { item, canWrite: true });
		await expect.element(page.getByRole('checkbox')).toBeChecked();
	});
});

// ─── ProgressRing & ContributorStack ─────────────────────────────────────────

describe('DocumentRow – progress ring and contributors', () => {
	it('renders the completion percentage label', async () => {
		const item = makeItem({ completionPercentage: 42 });
		render(DocumentRow, { item });
		await expect.element(page.getByText('42%').first()).toBeInTheDocument();
	});

	it('renders contributor initials when contributors present', async () => {
		const item = makeItem({
			contributors: [{ initials: 'AR', color: '#4a90e2', name: 'Anna Raddatz' }]
		});
		render(DocumentRow, { item });
		await expect.element(page.getByText('AR').first()).toBeInTheDocument();
	});
});
