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

import ChronikFuerDichBox from './ChronikFuerDichBox.svelte';
import type { NotificationItem } from '$lib/notification/notifications.svelte';

const mockFormResult = vi.hoisted(() => ({ type: 'success' as string }));

vi.mock('$app/forms', () => ({
	enhance(
		node: HTMLFormElement,
		submit?: (opts: {
			formData: FormData;
		}) => (opts: {
			result: { type: string; data?: Record<string, unknown> };
			update: () => Promise<void>;
		}) => Promise<void>
	) {
		const handler = async (e: Event) => {
			e.preventDefault();
			const cb = submit?.({ formData: new FormData(node) } as never);
			if (typeof cb === 'function') {
				await (
					cb as (o: { result: typeof mockFormResult; update: () => Promise<void> }) => Promise<void>
				)({ result: mockFormResult, update: async () => {} });
			}
		};
		node.addEventListener('submit', handler);
		return { destroy: () => node.removeEventListener('submit', handler) };
	}
}));

afterEach(() => {
	cleanup();
	mockFormResult.type = 'success';
});

function notif(partial: Partial<NotificationItem>): NotificationItem {
	return {
		id: 'n1',
		type: 'MENTION',
		documentId: 'doc-1',
		documentTitle: 'Ein Dokument',
		referenceId: 'ref-1',
		annotationId: null,
		read: false,
		createdAt: new Date(Date.now() - 5 * 60_000).toISOString(),
		actorName: 'Anna',
		...partial
	};
}

describe('ChronikFuerDichBox', () => {
	it('renders inbox-zero state when there are no unread items', async () => {
		render(ChronikFuerDichBox, {
			unread: [],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		const zero = document.querySelector('[data-testid="chronik-inbox-zero"]');
		expect(zero).not.toBeNull();
		await expect.element(page.getByText('Keine neuen Erwähnungen')).toBeInTheDocument();
	});

	it('links to the archived mentions in the inbox-zero state', async () => {
		render(ChronikFuerDichBox, {
			unread: [],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		const link = document.querySelector('a[href="/aktivitaeten?filter=fuer-dich"]');
		expect(link).not.toBeNull();
	});

	it('renders the count badge with correct total when unread exists', async () => {
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'a' }), notif({ id: 'b' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		await expect.element(page.getByText('2 neu')).toBeInTheDocument();
	});

	it('count badge has aria-live=polite when unread exists', async () => {
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'a' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		// Wait for render
		await expect.element(page.getByText('1 neu')).toBeInTheDocument();
		const badge = document.querySelector('[data-testid="chronik-fuerdich-count"]');
		expect(badge?.getAttribute('aria-live')).toBe('polite');
		expect(badge?.getAttribute('aria-atomic')).toBe('true');
	});

	it('does not render the "Alle gelesen" button when there are no unread items', async () => {
		render(ChronikFuerDichBox, {
			unread: [],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		await expect.element(page.getByText('Keine neuen Erwähnungen')).toBeInTheDocument();
		const all = document.querySelector('[data-testid="chronik-mark-all-read"]');
		expect(all).toBeNull();
	});

	it('renders the "Alle gelesen" button when unread exists', async () => {
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'a' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		await expect.element(page.getByText('Alle gelesen')).toBeInTheDocument();
	});

	it('calls optimisticMarkAllRead when the "Alle gelesen" button is submitted', async () => {
		const optimisticMarkAllRead = vi.fn();
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'a' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead
		});
		await userEvent.click(page.getByText('Alle gelesen'));
		expect(optimisticMarkAllRead).toHaveBeenCalledTimes(1);
	});

	it('calls optimisticMarkRead with the notification id when its dismiss button is submitted', async () => {
		const optimisticMarkRead = vi.fn();
		const n = notif({ id: 'xyz' });
		render(ChronikFuerDichBox, {
			unread: [n],
			optimisticMarkRead,
			optimisticMarkAllRead: vi.fn()
		});
		const dismiss = document.querySelector(
			'[data-testid="chronik-fuerdich-dismiss"]'
		) as HTMLButtonElement | null;
		expect(dismiss).not.toBeNull();
		dismiss?.click();
		expect(optimisticMarkRead).toHaveBeenCalledTimes(1);
		expect(optimisticMarkRead.mock.calls[0][0]).toBe('xyz');
	});

	it('mention row href includes both commentId and annotationId when annotationId is present', async () => {
		render(ChronikFuerDichBox, {
			unread: [
				notif({
					id: 'n-link',
					documentId: 'doc-42',
					referenceId: 'comment-7',
					annotationId: 'annot-9'
				})
			],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		const link = document.querySelector(
			'a[href="/documents/doc-42?commentId=comment-7&annotationId=annot-9"]'
		);
		expect(link).not.toBeNull();
	});

	it('Dismiss button is a sibling of the document link, never nested inside <a>', async () => {
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'x' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		const dismiss = document.querySelector('[data-testid="chronik-fuerdich-dismiss"]');
		expect(dismiss).not.toBeNull();
		// HTML spec forbids interactive content descendants of <a>.
		// Prevents the senior-audience tap-drag bug flagged by Leonie.
		expect(dismiss?.closest('a')).toBeNull();
	});

	it('shows an accessible error banner when the dismiss action returns a failure', async () => {
		mockFormResult.type = 'failure';
		render(ChronikFuerDichBox, {
			unread: [notif({ id: 'err-1' })],
			optimisticMarkRead: vi.fn(),
			optimisticMarkAllRead: vi.fn()
		});
		const dismiss = document.querySelector(
			'[data-testid="chronik-fuerdich-dismiss"]'
		) as HTMLButtonElement | null;
		expect(dismiss).not.toBeNull();
		dismiss?.click();
		// Allow microtask queue to flush
		await new Promise((r) => setTimeout(r, 0));
		const alert = document.querySelector('[role="alert"]');
		expect(alert).not.toBeNull();
	});
});
