import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import EventForm from './EventForm.svelte';
import {
	createConfirmService,
	CONFIRM_KEY,
	type ConfirmService
} from '$lib/shared/services/confirm.svelte.js';
import type { components } from '$lib/generated/api';

afterEach(() => {
	cleanup();
	vi.unstubAllGlobals();
});

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

/**
 * EventForm captures the confirm service at init (`getConfirmService()`), so every
 * render must provide a CONFIRM_KEY context — mirrors documents/[id]/edit's spec.
 * The returned service is handed back so delete tests can drive `settle()`.
 */
function renderForm(props: Record<string, unknown> = {}): ConfirmService {
	const service = createConfirmService();
	render(EventForm, { props, context: new Map([[CONFIRM_KEY, service]]) });
	return service;
}

/**
 * Minimal TimelineEventView shape used to seed the edit form. Mirrors
 * components['schemas']['TimelineEventView'] — all server-populated fields.
 */
function makeEvent(overrides: Partial<TimelineEventView> = {}): TimelineEventView {
	return {
		id: 'e1',
		title: 'Umzug nach Berlin',
		type: 'PERSONAL',
		eventDate: '1925-04-01',
		precision: 'DAY',
		version: 0,
		createdBy: 'u1',
		createdAt: '2026-01-01T00:00:00Z',
		updatedBy: 'u1',
		updatedAt: '2026-01-01T00:00:00Z',
		persons: [],
		documents: [],
		...overrides
	};
}

describe('EventForm — date precision RANGE reveal (headline AC, REQ-008/009)', () => {
	it('reveals the end-date field when precision is RANGE', async () => {
		renderForm({ event: makeEvent({ precision: 'RANGE', eventDateEnd: '1925-05-01' }) });
		await expect.element(page.getByLabelText('Enddatum')).toBeVisible();
	});

	it('hides the end-date field when precision is YEAR', async () => {
		renderForm({ event: makeEvent({ precision: 'YEAR' }) });
		await expect.element(page.getByTestId('end-date-region')).toBeInTheDocument();
		await expect.element(page.getByLabelText('Enddatum')).not.toBeInTheDocument();
	});
});

describe('EventForm — type seeding (review #8 refactor fence)', () => {
	it('seeds the type selector from the event so the submitted type is correct', async () => {
		renderForm({ event: makeEvent({ type: 'HISTORICAL' }) });
		const hidden = document.querySelector('input[type="hidden"][name="type"]') as HTMLInputElement;
		expect(hidden.value).toBe('HISTORICAL');
	});

	it('prefers the fail-payload type over the seeded event', async () => {
		renderForm({ event: makeEvent({ type: 'PERSONAL' }), form: { type: 'HISTORICAL' } });
		const hidden = document.querySelector('input[type="hidden"][name="type"]') as HTMLInputElement;
		expect(hidden.value).toBe('HISTORICAL');
	});
});

describe('EventForm — picker preselect (REQ-014)', () => {
	it('preselects a person when initialPersons is provided', async () => {
		renderForm({ initialPersons: [{ id: 'p1', displayName: 'Anna Müller' }] });
		await expect.element(page.getByText('Anna Müller')).toBeInTheDocument();
	});
});

describe('EventForm — required-field error (REQ-010)', () => {
	it('shows a required-field error when title is blank and save is attempted', async () => {
		renderForm({});
		await page.getByRole('button', { name: 'Speichern' }).click();
		await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument();
	});

	it('cancels the submission — fires no network POST — when title is blank', async () => {
		// The client-side guard must actually CANCEL the enhance submission, not just
		// show a message: enhance ignores onsubmit preventDefault(), so without cancel()
		// a blank-title Save still POSTs (and update()->applyAction crashes with no app).
		const fetchSpy = vi.fn(() => new Promise<Response>(() => {}));
		vi.stubGlobal('fetch', fetchSpy);
		renderForm({});
		await page.getByRole('button', { name: 'Speichern' }).click();
		await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument();
		expect(fetchSpy).not.toHaveBeenCalled();
	});

	it('rehydrates the pickers from the fail payload (Decision 6)', async () => {
		renderForm({
			form: {
				titleError: 'Bitte einen Titel eingeben.',
				title: '',
				persons: [{ id: 'p1', displayName: 'Anna Müller' }],
				documents: [{ id: 'd1', title: 'Brief A', documentDate: '1925-04-01' }]
			}
		});
		await expect.element(page.getByText('Anna Müller')).toBeInTheDocument();
		await expect.element(page.getByText(/Brief A/)).toBeInTheDocument();
	});
});

describe('EventForm — delete is gated behind confirmation (REQ-006)', () => {
	it('fires no DELETE until the user confirms, and not at all if they cancel', async () => {
		// The DELETE must wait for the confirm dialog. enhance ignores onsubmit
		// preventDefault(), so the only correct gate is awaiting confirm() inside the
		// enhance submit phase and calling cancel() on a "no" — anything else POSTs
		// the destructive DELETE on the bare click, before the dialog is answered.
		const fetchSpy = vi.fn(() => new Promise<Response>(() => {}));
		vi.stubGlobal('fetch', fetchSpy);
		const service = renderForm({ event: makeEvent() });

		await page.getByRole('button', { name: 'Löschen' }).click();
		// Dialog is pending — nothing must have been POSTed yet.
		expect(fetchSpy).not.toHaveBeenCalled();

		service.settle(false); // user cancels
		await new Promise((r) => setTimeout(r, 0));
		expect(fetchSpy).not.toHaveBeenCalled();
	});

	it('fires the DELETE once the user confirms', async () => {
		const fetchSpy = vi.fn(() => new Promise<Response>(() => {}));
		vi.stubGlobal('fetch', fetchSpy);
		const service = renderForm({ event: makeEvent() });

		await page.getByRole('button', { name: 'Löschen' }).click();
		service.settle(true); // user confirms
		await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalled());
	});
});

describe('EventForm — seeds the When-section from the fail payload (review #2 — no-JS)', () => {
	it('re-seeds date, precision and end-date from the form payload on /new', async () => {
		renderForm({
			form: { eventDate: '1944-03-12', precision: 'RANGE', eventDateEnd: '1944-03-14' }
		});

		// precision=RANGE seeded → end-date field revealed (proves precision survived).
		await expect.element(page.getByLabelText('Enddatum')).toBeVisible();

		const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
		expect(dateInput.value).toBe('12.03.1944');
		const endInput = document.querySelector('#eventDateEnd') as HTMLInputElement;
		expect(endInput.value).toBe('14.03.1944');
	});
});

describe('EventForm — RANGE end-date required error wired per-field (review #3)', () => {
	it('shows the end-date required message on the end-date field, marked invalid', async () => {
		renderForm({
			form: {
				precision: 'RANGE',
				eventDate: '1925-04-01',
				endDateError: 'Bitte ein Enddatum eingeben.'
			}
		});
		await expect.element(page.getByText('Bitte ein Enddatum eingeben.')).toBeInTheDocument();
		const endInput = document.querySelector('#eventDateEnd') as HTMLInputElement;
		expect(endInput.getAttribute('aria-invalid')).toBe('true');
	});
});

describe('EventForm — server date error wired per-field (REQ-011)', () => {
	it('marks the date field aria-invalid and shows the message on a server date error', async () => {
		renderForm({ form: { dateError: 'Bitte ein Datum eingeben.' } });
		await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument();
		const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
		expect(dateInput.getAttribute('aria-invalid')).toBe('true');
	});
});

describe('EventForm — validation errors clear on correction (review #4)', () => {
	it('clears the server title error once a valid title is entered', async () => {
		renderForm({ form: { titleError: 'Bitte einen Titel eingeben.', title: '' } });
		await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument();

		const titleInput = document.querySelector('#event-title') as HTMLInputElement;
		titleInput.value = 'Ein Titel';
		titleInput.dispatchEvent(new Event('input', { bubbles: true }));

		await expect.element(page.getByText('Bitte einen Titel eingeben.')).not.toBeInTheDocument();
	});

	it('clears the server date error once a valid date is entered', async () => {
		renderForm({ form: { dateError: 'Bitte ein Datum eingeben.' } });
		await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument();

		const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
		dateInput.value = '01.04.1925';
		dateInput.dispatchEvent(new Event('input', { bubbles: true }));

		await expect.element(page.getByText('Bitte ein Datum eingeben.')).not.toBeInTheDocument();
	});
});

describe('EventForm — submitting state (named AC, Decision 8)', () => {
	it('disables the submit button while submitting', async () => {
		// A never-resolving fetch keeps use:enhance in flight so the disabled
		// transition (the double-submit guard) is observable rather than racing the
		// reset in the result callback.
		vi.stubGlobal(
			'fetch',
			vi.fn(() => new Promise(() => {}))
		);
		renderForm({ event: makeEvent() });
		const btn = page.getByRole('button', { name: 'Speichern' });
		await expect.element(btn).not.toBeDisabled();
		await btn.click();
		await expect.element(btn).toBeDisabled();
	});
});

describe('EventForm — server error surfaced inline (REQ-007/013)', () => {
	it('renders the mapped error from the form prop', async () => {
		renderForm({ event: makeEvent(), form: { error: 'Etwas ist schiefgelaufen.' } });
		await expect.element(page.getByText('Etwas ist schiefgelaufen.')).toBeInTheDocument();
	});
});
