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

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

afterEach(cleanup);

// ─── Test data ────────────────────────────────────────────────────────────────

const baseData = {
	user: undefined,
	canWrite: true,
	canAnnotate: false,
	canBlogWrite: false,
	documents: [],
	initialValues: { senderName: '', receiverName: '' },
	filters: { senderId: '', receiverId: '', from: '', to: '', dir: 'DESC' as const }
};

const withSender = {
	...baseData,
	initialValues: { senderName: 'Hans Müller', receiverName: '' },
	filters: { ...baseData.filters, senderId: 'p1' }
};

const withPersons = {
	...baseData,
	initialValues: { senderName: 'Hans Müller', receiverName: 'Anna Schmidt' },
	filters: { ...baseData.filters, senderId: 'p1', receiverId: 'p2' }
};

const makePerson = (overrides: Record<string, unknown> = {}) => ({
	id: 'p1',
	firstName: 'Hans',
	lastName: 'Müller',
	personType: 'PERSON' as const,
	familyMember: false,
	displayName: 'Hans Müller',
	...overrides
});

const hansPerson = makePerson();
const annaPerson = makePerson({
	id: 'p2',
	firstName: 'Anna',
	lastName: 'Schmidt',
	displayName: 'Anna Schmidt'
});

const makeDoc = (overrides: Record<string, unknown> = {}) => ({
	id: 'd1',
	title: 'Testbrief',
	originalFilename: 'testbrief.pdf',
	status: 'UPLOADED' as const,
	documentDate: '1923-04-12',
	location: 'Berlin',
	metadataComplete: false,
	scriptType: 'UNKNOWN' as const,
	sender: makePerson(),
	receivers: [
		makePerson({
			id: 'p2',
			firstName: 'Anna',
			lastName: 'Schmidt',
			displayName: 'Anna Schmidt'
		})
	],
	tags: [],
	transcription: undefined,
	filePath: undefined,
	createdAt: '1923-04-12T00:00:00Z',
	updatedAt: '1923-04-12T00:00:00Z',
	...overrides
});

const withDocs = {
	...withPersons,
	documents: [makeDoc()]
};

// ─── Hero state (no senderId) ────────────────────────────────────────────────

describe('Briefwechsel page – hero state', () => {
	it('shows the hero when no person is selected', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument();
	});

	it('shows the discovery headline', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByText(/Wessen Briefe möchten Sie lesen/i)).toBeInTheDocument();
	});

	it('does not show the person bar in hero state', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument();
		await expect.element(page.getByTestId('conv-person-bar')).not.toBeInTheDocument();
	});

	it('does not show filter controls in hero state', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument();
		await expect.element(page.getByTestId('conv-filter-controls')).not.toBeInTheDocument();
	});

	it('does not show the new document link when no person is selected', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByTestId('conv-new-doc-link')).not.toBeInTheDocument();
	});

	it('does not show a year divider when no person is selected', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByTestId('year-divider')).not.toBeInTheDocument();
	});
});

// ─── Results state (senderId set) ────────────────────────────────────────────

describe('Briefwechsel page – results state', () => {
	it('does not show the hero when senderId is set', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByTestId('conv-person-bar')).toBeInTheDocument();
		await expect.element(page.getByTestId('conv-hero')).not.toBeInTheDocument();
	});

	it('shows the person bar when senderId is set', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByTestId('conv-person-bar')).toBeInTheDocument();
	});

	it('hides filter controls by default (collapsible)', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByTestId('conv-person-bar')).toBeInTheDocument();
		await expect.element(page.getByTestId('conv-filter-controls')).not.toBeInTheDocument();
	});
});

// ─── Recent persons chips ─────────────────────────────────────────────────────

describe('Briefwechsel page – recent persons', () => {
	it('shows recent person chips from localStorage', async () => {
		localStorage.setItem(
			'korrespondenz_recent_persons',
			JSON.stringify([{ id: 'r1', name: 'Clara Braun' }])
		);
		render(Page, { data: baseData });
		await expect.element(page.getByText('Clara Braun')).toBeInTheDocument();
		localStorage.removeItem('korrespondenz_recent_persons');
	});

	it('does not crash when localStorage contains corrupt JSON', async () => {
		localStorage.setItem('korrespondenz_recent_persons', '}{not valid json');
		render(Page, { data: baseData });
		await expect.element(page.getByText(/Wessen Briefe möchten Sie lesen/i)).toBeInTheDocument();
		localStorage.removeItem('korrespondenz_recent_persons');
	});
});

// ─── Single-person hint bar ───────────────────────────────────────────────────

describe('Briefwechsel page – single-person hint bar', () => {
	it('shows hint bar when only senderId is set', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByText(/Alle Briefe von Hans Müller/i)).toBeInTheDocument();
	});

	it('does not show hint bar when both persons are set', async () => {
		render(Page, { data: { ...withPersons, documents: [makeDoc()] } });
		await expect.element(page.getByText(/Alle Briefe von Hans Müller/i)).not.toBeInTheDocument();
	});

	it('does not show hint bar when no person is set', async () => {
		render(Page, { data: baseData });
		await expect.element(page.getByText(/Alle Briefe von/i)).not.toBeInTheDocument();
	});
});

// ─── Strip letter count ───────────────────────────────────────────────────────

describe('Briefwechsel page – strip letter count', () => {
	it('shows 0 Briefe when senderId is set but no documents', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByTestId('conv-strip-count')).toHaveTextContent('0 Briefe');
	});

	it('shows correct count when documents are loaded', async () => {
		render(Page, { data: { ...withPersons, documents: [makeDoc()] } });
		await expect.element(page.getByTestId('conv-strip-count')).toHaveTextContent('1 Briefe');
	});
});

// ─── No results ───────────────────────────────────────────────────────────────

describe('Briefwechsel page – no results', () => {
	it('shows "no documents found" when a person is selected but there are no documents', async () => {
		render(Page, { data: withSender });
		await expect.element(page.getByText(/Keine Dokumente gefunden/i)).toBeInTheDocument();
	});
});

// ─── Swap button ──────────────────────────────────────────────────────────────

describe('Briefwechsel page – swap button', () => {
	it('swap button is invisible when only one person is set', async () => {
		render(Page, { data: withSender });
		const btn = document.querySelector<HTMLElement>('[data-testid="conv-swap-btn"]');
		expect(btn).not.toBeNull();
		expect(btn!.className).toMatch(/opacity-0/);
	});

	it('swap button is visible when both persons are set', async () => {
		render(Page, { data: withPersons });
		const btn = document.querySelector<HTMLElement>('[data-testid="conv-swap-btn"]');
		expect(btn).not.toBeNull();
		expect(btn!.className).not.toMatch(/opacity-0/);
	});

	it('calls goto with swapped sender and receiver when clicked', async () => {
		const { goto } = await import('$app/navigation');
		vi.mocked(goto).mockClear();
		render(Page, { data: withPersons });
		document.querySelector<HTMLElement>('[data-testid="conv-swap-btn"]')!.click();
		expect(goto).toHaveBeenCalledWith(expect.stringContaining('senderId=p2'), expect.anything());
		expect(goto).toHaveBeenCalledWith(expect.stringContaining('receiverId=p1'), expect.anything());
	});
});

// ─── Distribution bar (bilateral only) ────────────────────────────────────────

describe('Briefwechsel page – distribution bar', () => {
	it('renders the DistributionBar when both persons are set and there are documents', async () => {
		const data = {
			...withPersons,
			documents: [
				makeDoc({ id: 'out1', sender: hansPerson, receivers: [annaPerson] }),
				makeDoc({ id: 'in1', sender: annaPerson, receivers: [hansPerson] }),
				makeDoc({ id: 'in2', sender: annaPerson, receivers: [hansPerson] })
			]
		};
		render(Page, { data });
		const bar = document.querySelector('[role="img"]');
		expect(bar).not.toBeNull();
		const label = bar!.getAttribute('aria-label') ?? '';
		expect(label).toContain('Hans Müller');
		expect(label).toContain('Anna Schmidt');
		expect(label).toMatch(/\b1\b/);
		expect(label).toMatch(/\b2\b/);
	});

	it('does not render the DistributionBar in single-person mode', async () => {
		render(Page, { data: { ...withSender, documents: [makeDoc()] } });
		const bar = document.querySelector('[role="img"]');
		expect(bar).toBeNull();
	});

	it('renders a ConversationThumbnail tile for each document in the list', async () => {
		// A broken `{#each}` wiring in ConversationTimeline would silently stop
		// rendering rows while the DistributionBar above it kept working. Assert
		// the per-row tile so that class of regression is caught.
		const data = {
			...withPersons,
			documents: [makeDoc({ id: 'd-a' }), makeDoc({ id: 'd-b' }), makeDoc({ id: 'd-c' })]
		};
		render(Page, { data });
		const tiles = document.querySelectorAll('[data-testid="conv-thumb-tile"]');
		expect(tiles).toHaveLength(3);
	});
});

// ─── Year dividers ────────────────────────────────────────────────────────────

describe('Briefwechsel page – year dividers', () => {
	it('renders a year divider for the first document', async () => {
		render(Page, { data: withDocs });
		await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
	});

	it('renders a divider for each new year in the document list', async () => {
		const data = {
			...withPersons,
			documents: [
				makeDoc({ documentDate: '1923-04-12' }),
				makeDoc({ id: 'd2', documentDate: '1965-08-03' })
			]
		};
		render(Page, { data });
		await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
		await expect.element(page.getByTestId('year-divider').nth(1)).toHaveTextContent('1965');
	});

	it('does not render a second divider for documents from the same year', async () => {
		const data = {
			...withPersons,
			documents: [
				makeDoc({ documentDate: '1923-04-12' }),
				makeDoc({ id: 'd2', documentDate: '1923-09-01' })
			]
		};
		render(Page, { data });
		await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
		await expect.element(page.getByTestId('year-divider').nth(1)).not.toBeInTheDocument();
	});
});

// ─── New document link ────────────────────────────────────────────────────────

describe('Briefwechsel page – new document link', () => {
	it('shows the link with correct href for a write user (bilateral)', async () => {
		render(Page, { data: { ...withDocs, canWrite: true } });
		const link = page.getByTestId('conv-new-doc-link');
		await expect.element(link).toBeInTheDocument();
		await expect.element(link).toHaveAttribute('href', expect.stringContaining('senderId=p1'));
		await expect.element(link).toHaveAttribute('href', expect.stringContaining('receiverId=p2'));
	});

	it('shows the link with correct href for single-person mode', async () => {
		render(Page, { data: { ...withSender, documents: [makeDoc()], canWrite: true } });
		const link = page.getByTestId('conv-new-doc-link');
		await expect.element(link).toBeInTheDocument();
		await expect.element(link).toHaveAttribute('href', expect.stringContaining('senderId=p1'));
		await expect.element(link).not.toHaveAttribute('href', expect.stringContaining('receiverId'));
	});

	it('hides the link for a read-only user', async () => {
		render(Page, { data: { ...withDocs, canWrite: false } });
		await expect.element(page.getByTestId('conv-new-doc-link')).not.toBeInTheDocument();
	});
});
