import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const baseUrl = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:4173';
const urlMatch = baseUrl.match(/:(\d+)$/);
const PORT = urlMatch ? urlMatch[1] : '4173';
const ALPHA_URL = `http://ecole-alpha.classeo.local:${PORT}`;

const TEACHER_EMAIL = 'e2e-homework-teacher@example.com';
const TEACHER_PASSWORD = 'HomeworkTest123';
const TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

const projectRoot = join(__dirname, '../..');
const composeFile = join(projectRoot, 'compose.yaml');

function runSql(sql: string) {
	execSync(
		`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "${sql}" 2>&1`,
		{ encoding: 'utf-8' }
	);
}

function clearCache() {
	try {
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear paginated_queries.cache 2>&1`,
			{ encoding: 'utf-8' }
		);
	} catch {
		// Cache pool may not exist
	}
}

function resolveDeterministicIds(): { schoolId: string; academicYearId: string } {
	const output = execSync(
		`docker compose -f "${composeFile}" exec -T php php -r '` +
			`require "/app/vendor/autoload.php"; ` +
			`$t="${TENANT_ID}"; $ns="6ba7b814-9dad-11d1-80b4-00c04fd430c8"; ` +
			`echo Ramsey\\Uuid\\Uuid::uuid5($ns,"school-$t")->toString()."\\n"; ` +
			`$m=(int)date("n"); $s=$m>=9?(int)date("Y"):(int)date("Y")-1; $e=$s+1; ` +
			`echo Ramsey\\Uuid\\Uuid::uuid5($ns,"$t:$s-$e")->toString();` +
			`' 2>&1`,
		{ encoding: 'utf-8' }
	).trim();
	const [schoolId, academicYearId] = output.split('\n');
	return { schoolId: schoolId!, academicYearId: academicYearId! };
}

/**
 * Returns a future weekday date string (YYYY-MM-DD).
 * Skips weekends to satisfy DueDateValidator.
 */
function getNextWeekday(daysFromNow: number): string {
	const date = new Date();
	date.setDate(date.getDate() + daysFromNow);
	// Skip to Monday if weekend
	const day = date.getDay();
	if (day === 0) date.setDate(date.getDate() + 1); // Sunday → Monday
	if (day === 6) date.setDate(date.getDate() + 2); // Saturday → Monday
	// Use local date components to avoid UTC timezone shift from toISOString()
	const y = date.getFullYear();
	const m = String(date.getMonth() + 1).padStart(2, '0');
	const d = String(date.getDate()).padStart(2, '0');
	return `${y}-${m}-${d}`;
}

async function loginAsTeacher(page: import('@playwright/test').Page) {
	await page.goto(`${ALPHA_URL}/login`);
	await page.locator('#email').fill(TEACHER_EMAIL);
	await page.locator('#password').fill(TEACHER_PASSWORD);
	await Promise.all([
		page.waitForURL(/\/dashboard/, { timeout: 60000 }),
		page.getByRole('button', { name: /se connecter/i }).click()
	]);
}

async function navigateToHomework(page: import('@playwright/test').Page) {
	await page.goto(`${ALPHA_URL}/dashboard/teacher/homework`);
	await expect(page.getByRole('heading', { name: /mes devoirs/i })).toBeVisible({ timeout: 15000 });
}

function seedTeacherAssignments() {
	const { academicYearId } = resolveDeterministicIds();
	try {
		runSql(
			`INSERT INTO teacher_assignments (id, tenant_id, teacher_id, school_class_id, subject_id, academic_year_id, status, start_date, created_at, updated_at) ` +
				`SELECT gen_random_uuid(), '${TENANT_ID}', u.id, c.id, s.id, '${academicYearId}', 'active', NOW(), NOW(), NOW() ` +
				`FROM users u CROSS JOIN school_classes c CROSS JOIN subjects s ` +
				`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
				`AND c.tenant_id = '${TENANT_ID}' ` +
				`AND s.tenant_id = '${TENANT_ID}' ` +
				`ON CONFLICT DO NOTHING`
		);
	} catch {
		// Table may not exist
	}
}

test.describe('Homework Management (Story 5.1)', () => {
	test.beforeAll(async () => {
		// Clear rate limiter to prevent login throttling across serial tests
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Cache pool may not exist
		}

		// Create teacher user
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${TEACHER_EMAIL} --password=${TEACHER_PASSWORD} --role=ROLE_PROF 2>&1`,
			{ encoding: 'utf-8' }
		);

		// Ensure classes and subject exist
		const { schoolId, academicYearId } = resolveDeterministicIds();
		try {
			runSql(
				`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6A', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
			runSql(
				`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6B', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
			runSql(
				`INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-HW-Maths', 'E2EMAT', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
		} catch {
			// May already exist
		}

		seedTeacherAssignments();
		clearCache();
	});

	test.beforeEach(async () => {
		// Clear rate limiter to prevent login throttling across tests
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Cache pool may not exist
		}

		// Clean up homework data (homework_submissions has NO CASCADE on homework_id,
		// so we must delete submissions first)
		try {
			runSql(`DELETE FROM submission_attachments WHERE submission_id IN (SELECT hs.id FROM homework_submissions hs JOIN homework h ON hs.homework_id = h.id WHERE h.tenant_id = '${TENANT_ID}' AND h.teacher_id IN (SELECT id FROM users WHERE email = '${TEACHER_EMAIL}' AND tenant_id = '${TENANT_ID}'))`);
		} catch { /* Table may not exist */ }
		try {
			runSql(`DELETE FROM homework_submissions WHERE homework_id IN (SELECT id FROM homework WHERE tenant_id = '${TENANT_ID}' AND teacher_id IN (SELECT id FROM users WHERE email = '${TEACHER_EMAIL}' AND tenant_id = '${TENANT_ID}'))`);
		} catch { /* Table may not exist */ }
		try {
			runSql(`DELETE FROM homework WHERE tenant_id = '${TENANT_ID}' AND teacher_id IN (SELECT id FROM users WHERE email = '${TEACHER_EMAIL}' AND tenant_id = '${TENANT_ID}')`);
		} catch {
			// Table may not exist
		}

		// Disable any homework rules left by other test files (homework-rules-warning,
		// homework-rules-hard) to prevent rule warnings/blocks in duplicate tests.
		try {
			runSql(`UPDATE homework_rules SET enabled = false, updated_at = NOW() WHERE tenant_id = '${TENANT_ID}'`);
		} catch { /* Table may not exist */ }

		// Clear school calendar entries that may block dates (Vacances de Printemps, etc.)
		try {
			runSql(`DELETE FROM school_calendar_entries WHERE tenant_id = '${TENANT_ID}'`);
		} catch { /* Table may not exist */ }

		// Re-ensure data exists
		const { schoolId, academicYearId } = resolveDeterministicIds();
		try {
			runSql(
				`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6A', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
			runSql(
				`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6B', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
			runSql(
				`INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at) ` +
					`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-HW-Maths', 'E2EMAT', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
			);
		} catch {
			// May already exist
		}

		seedTeacherAssignments();
		clearCache();
	});

	// ============================================================================
	// Navigation
	// ============================================================================
	test.describe('Navigation', () => {
		test('homework link appears in teacher navigation', async ({ page }) => {
			await loginAsTeacher(page);
			const nav = page.locator('.desktop-nav');
			await expect(nav.getByRole('link', { name: /devoirs/i })).toBeVisible({ timeout: 15000 });
		});

		test('can navigate to homework page', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await expect(page.getByRole('heading', { name: /mes devoirs/i })).toBeVisible();
		});
	});

	// ============================================================================
	// Empty State
	// ============================================================================
	test.describe('Empty State', () => {
		test('shows empty state when no homework exists', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await expect(page.getByText(/aucun devoir/i)).toBeVisible({ timeout: 10000 });
		});
	});

	// ============================================================================
	// AC1: Create Homework
	// ============================================================================
	test.describe('AC1: Create Homework', () => {
		test('can create a new homework', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Open create modal
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Select class
			const classSelect = page.locator('#hw-class');
			await expect(classSelect).toBeVisible();
			await classSelect.selectOption({ index: 1 });

			// Select subject (AC2: filtered by class)
			const subjectSelect = page.locator('#hw-subject');
			await expect(subjectSelect).toBeEnabled();
			await subjectSelect.selectOption({ index: 1 });

			// Fill title
			await page.locator('#hw-title').fill('Exercices chapitre 5');

			// Fill description (TipTap initializes asynchronously)
			const editorContent = page.locator('.modal .rich-text-content');
			await expect(editorContent).toBeVisible({ timeout: 10000 });
			await editorContent.click();
			await editorContent.pressSequentially('Pages 42-45, exercices 1 à 10');

			// Set due date (next weekday, at least 2 days from now)
			await page.locator('#hw-due-date').fill(getNextWeekday(3));

			// Submit
			await page.getByRole('button', { name: /créer le devoir/i }).click();

			// Modal closes and homework appears in list
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Exercices chapitre 5')).toBeVisible({ timeout: 10000 });
		});

		test('cancel closes the modal without creating', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			await page.getByRole('button', { name: /annuler/i }).click();
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
		});
	});

	// ============================================================================
	// AC2: Subject Filtering
	// ============================================================================
	test.describe('AC2: Subject Filtering', () => {
		test('subject dropdown is disabled until class is selected', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Subject should be disabled initially
			await expect(page.locator('#hw-subject')).toBeDisabled();

			// Select class
			await page.locator('#hw-class').selectOption({ index: 1 });

			// Subject should now be enabled
			await expect(page.locator('#hw-subject')).toBeEnabled();
		});
	});

	// ============================================================================
	// AC5: Published Homework Appears in List
	// ============================================================================
	test.describe('AC5: Published Homework', () => {
		test('created homework appears in list with correct info', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Create a homework
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			await page.locator('#hw-class').selectOption({ index: 1 });
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill('Devoir de mathématiques');
			await page.locator('#hw-due-date').fill(getNextWeekday(5));
			await page.getByRole('button', { name: /créer le devoir/i }).click();

			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Verify homework card shows
			await expect(page.getByText('Devoir de mathématiques')).toBeVisible({ timeout: 10000 });
			await expect(page.getByText(/publié/i)).toBeVisible();
		});
	});

	// ============================================================================
	// AC6: Edit Homework
	// ============================================================================
	test.describe('AC6: Edit Homework', () => {
		test('can modify an existing homework', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// First create a homework
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
			await page.locator('#hw-class').selectOption({ index: 1 });
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill('Devoir à modifier');
			await page.locator('#hw-due-date').fill(getNextWeekday(5));
			await page.getByRole('button', { name: /créer le devoir/i }).click();
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Devoir à modifier')).toBeVisible({ timeout: 10000 });

			// Click edit
			await page.getByRole('button', { name: /modifier/i }).first().click();
			const editDialog = page.getByRole('dialog');
			await expect(editDialog).toBeVisible({ timeout: 10000 });

			// Change title
			await page.locator('#edit-title').clear();
			await page.locator('#edit-title').fill('Devoir modifié');

			// Save
			await page.getByRole('button', { name: /enregistrer/i }).click();
			await expect(editDialog).not.toBeVisible({ timeout: 10000 });

			// Verify updated title
			await expect(page.getByText('Devoir modifié')).toBeVisible({ timeout: 10000 });
		});

		test('can delete an existing homework', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// First create a homework
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
			await page.locator('#hw-class').selectOption({ index: 1 });
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill('Devoir à supprimer');
			await page.locator('#hw-due-date').fill(getNextWeekday(5));
			await page.getByRole('button', { name: /créer le devoir/i }).click();
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Devoir à supprimer')).toBeVisible({ timeout: 10000 });

			// Click delete
			await page.getByRole('button', { name: /supprimer/i }).first().click();

			// Confirm in alertdialog
			const confirmDialog = page.getByRole('alertdialog');
			await expect(confirmDialog).toBeVisible({ timeout: 5000 });
			await expect(page.getByText(/êtes-vous sûr/i)).toBeVisible();

			await confirmDialog.getByRole('button', { name: /supprimer/i }).click();
			await expect(confirmDialog).not.toBeVisible({ timeout: 10000 });

			// Homework should be gone from the list (or show as deleted)
			await expect(page.getByText('Devoir à supprimer')).not.toBeVisible({ timeout: 10000 });
		});
	});

	// ============================================================================
	// Date Validation
	// ============================================================================
	test.describe('Date Validation', () => {
		test('due date input has min attribute set to tomorrow', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			const dueDateInput = page.locator('#hw-due-date');
			const minValue = await dueDateInput.getAttribute('min');
			const tomorrow = new Date();
			tomorrow.setDate(tomorrow.getDate() + 1);
			const ey = tomorrow.getFullYear();
			const em = String(tomorrow.getMonth() + 1).padStart(2, '0');
			const ed = String(tomorrow.getDate()).padStart(2, '0');
			expect(minValue).toBe(`${ey}-${em}-${ed}`);
		});

		test('backend rejects a past due date', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			await page.locator('#hw-class').selectOption({ index: 1 });
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill('Devoir date passée');
			const editorVal = page.locator('.modal .rich-text-content');
			await expect(editorVal).toBeVisible({ timeout: 10000 });
			await editorVal.click();
			await editorVal.pressSequentially('Test validation');

			// Set a past weekday — must be Mon-Fri to avoid frontend weekend validation
			const pastDay = new Date();
			do {
				pastDay.setDate(pastDay.getDate() - 1);
			} while (pastDay.getDay() === 0 || pastDay.getDay() === 6);
			const y = pastDay.getFullYear();
			const m = String(pastDay.getMonth() + 1).padStart(2, '0');
			const d = String(pastDay.getDate()).padStart(2, '0');
			const pastDate = `${y}-${m}-${d}`;
			await page.locator('#hw-due-date').fill(pastDate);

			// Bypass HTML native validation (min attribute) to test backend validation
			await page.locator('form.modal-body').evaluate((el) => el.setAttribute('novalidate', ''));
			await page.getByRole('button', { name: /créer le devoir/i }).click();

			// Backend should reject — an error alert appears
			await expect(page.locator('.alert-error')).toBeVisible({ timeout: 10000 });
		});
	});

	// ============================================================================
	// AC: Duplicate Homework (Story 5.2)
	// ============================================================================
	test.describe('Story 5.2: Duplicate Homework', () => {
		test.beforeAll(async () => {
			// Ensure a second class exists for duplication
			const { schoolId, academicYearId } = resolveDeterministicIds();
			try {
				runSql(
					`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
						`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6B', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
				);
			} catch {
				// May already exist
			}

			seedTeacherAssignments();
			clearCache();
		});

		async function createHomework(page: import('@playwright/test').Page, title: string) {
			await createHomeworkInClass(page, title, { index: 1 });
		}

		async function createHomeworkInClass(
			page: import('@playwright/test').Page,
			title: string,
			classOption: { index: number } | { label: string },
		) {
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
			await page.locator('#hw-class').selectOption(classOption);
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill(title);
			await page.locator('#hw-due-date').fill(getNextWeekday(5));
			await page.getByRole('button', { name: /créer le devoir/i }).click();
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
			await expect(page.getByText(title)).toBeVisible({ timeout: 10000 });
		}

		test('duplicate button is visible on homework card', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir dupliquer test');

			await expect(page.getByRole('button', { name: /dupliquer/i })).toBeVisible();
		});

		test('opens duplicate modal with class selection', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir à dupliquer');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });
			await expect(dialog.getByText(/dupliquer le devoir/i)).toBeVisible();
			await expect(dialog.getByText('Devoir à dupliquer')).toBeVisible();
		});

		test('shows target classes checkboxes excluding source class', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir classes cibles');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Should show checkboxes for target classes
			const checkboxes = dialog.locator('input[type="checkbox"]');
			await expect(checkboxes.first()).toBeVisible({ timeout: 5000 });
		});

		test('can duplicate homework to a target class', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir duplication réussie');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select first target class
			const firstCheckbox = dialog.locator('input[type="checkbox"]').first();
			await firstCheckbox.check();

			// Click duplicate button
			await page.getByRole('button', { name: /dupliquer \(1 classe\)/i }).click();
			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Homework should now appear twice (original + duplicate)
			await expect(page.getByText('Devoir duplication réussie')).toHaveCount(2, { timeout: 10000 });
		});

		test('duplicate button is disabled when no class selected', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir sans sélection');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Duplicate button should be disabled
			const duplicateBtn = dialog.getByRole('button', { name: /dupliquer/i }).last();
			await expect(duplicateBtn).toBeDisabled();
		});

		test('cancel closes the duplicate modal', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir annulation dupli');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			await dialog.getByRole('button', { name: /annuler/i }).click();
			await expect(dialog).not.toBeVisible({ timeout: 5000 });
		});

		test('can duplicate homework to multiple classes', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir multi-dupli');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select ALL available checkboxes (at least 2 target classes)
			const checkboxes = dialog.locator('input[type="checkbox"]');
			const count = await checkboxes.count();
			expect(count).toBeGreaterThanOrEqual(1);

			for (let i = 0; i < count; i++) {
				await checkboxes.nth(i).check();
			}

			// Verify button text reflects the count
			const duplicateButton = dialog.getByRole('button', {
				name: new RegExp(`dupliquer \\(${count} classes?\\)`, 'i')
			});
			await expect(duplicateButton).toBeVisible();
			await expect(duplicateButton).toBeEnabled();

			// Click duplicate
			await duplicateButton.click();
			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Verify the homework title appears count+1 times (original + duplicates)
			await expect(page.getByText('Devoir multi-dupli')).toHaveCount(count + 1, { timeout: 10000 });
		});

		test('can customize due date per target class', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir date custom');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select the first checkbox
			const firstCheckbox = dialog.locator('input[type="checkbox"]').first();
			await firstCheckbox.check();

			// A date input should appear after checking the checkbox
			const dueDateInput = dialog.locator('input[type="date"]').first();
			await expect(dueDateInput).toBeVisible({ timeout: 5000 });

			// Change the due date to a custom value
			await dueDateInput.fill(getNextWeekday(10));

			// Click duplicate
			await page.getByRole('button', { name: /dupliquer \(1 classe\)/i }).click();
			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Verify the homework appears twice (original + 1 duplicate)
			await expect(page.getByText('Devoir date custom')).toHaveCount(2, { timeout: 10000 });
		});

		test('shows validation errors per class when duplication rules fail', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);
			await createHomework(page, 'Devoir validation règles');

			await page.getByRole('button', { name: /dupliquer/i }).first().click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select first target class
			const firstCheckbox = dialog.locator('input[type="checkbox"]').first();
			await firstCheckbox.check();

			// Intercept the POST duplicate API call to simulate a validation error
			await page.route(
				'**/api/homework/*/duplicate',
				async (route, request) => {
					// Extract targetClassIds from the request body
					const body = request.postDataJSON();
					const targetClassIds: string[] = body.targetClassIds ?? [];

					await route.fulfill({
						status: 400,
						contentType: 'application/json',
						body: JSON.stringify({
							error: 'Validation échouée pour certaines classes.',
							results: targetClassIds.map((classId: string) => ({
								classId,
								valid: false,
								error: "L'enseignant n'est pas affecté à cette classe pour cette matière.",
							})),
						}),
					});
				},
			);

			// Click duplicate
			await page.getByRole('button', { name: /dupliquer \(1 classe\)/i }).click();

			// Verify: error alert appears in the modal
			await expect(dialog.locator('.alert-error')).toBeVisible({ timeout: 10000 });
			await expect(dialog.getByText(/certaines classes ne passent pas la validation/i)).toBeVisible();

			// Verify: validation error message appears next to the class
			await expect(dialog.locator('.validation-error')).toBeVisible();
			await expect(
				dialog.getByText(/l'enseignant n'est pas affecté à cette classe/i),
			).toBeVisible();

			// Verify: modal stays open (not closed on validation error)
			await expect(dialog).toBeVisible();
		});

		test('can filter homework list by class', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Create homework in two different classes
			await createHomeworkInClass(page, 'Devoir classe A', { label: 'E2E-HW-6A' });
			await createHomeworkInClass(page, 'Devoir classe B', { label: 'E2E-HW-6B' });

			// Both visible initially
			await expect(page.getByText('Devoir classe A')).toBeVisible({ timeout: 5000 });
			await expect(page.getByText('Devoir classe B')).toBeVisible({ timeout: 5000 });

			// Filter by class A — class B homework must disappear
			const classFilter = page.locator('select[aria-label="Filtrer par classe"]');
			await classFilter.selectOption({ label: 'E2E-HW-6A' });
			await expect(page.getByText('Devoir classe A')).toBeVisible({ timeout: 5000 });
			await expect(page.getByText('Devoir classe B')).not.toBeVisible({ timeout: 5000 });

			// Reset — both visible again
			await classFilter.selectOption({ index: 0 });
			await expect(page.getByText('Devoir classe A')).toBeVisible({ timeout: 5000 });
			await expect(page.getByText('Devoir classe B')).toBeVisible({ timeout: 5000 });
		});
	});

	// ============================================================================
	// Partial Update
	// ============================================================================
	test.describe('Partial Update', () => {
		test('can update only the title without changing other fields', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			const dueDate = getNextWeekday(5);

			// Create a homework with description
			await page.getByRole('button', { name: /nouveau devoir/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
			await page.locator('#hw-class').selectOption({ index: 1 });
			await page.locator('#hw-subject').selectOption({ index: 1 });
			await page.locator('#hw-title').fill('Titre original');
			const editorEdit = page.locator('.modal .rich-text-content');
			await expect(editorEdit).toBeVisible({ timeout: 10000 });
			await editorEdit.click();
			await editorEdit.pressSequentially('Description inchangée');
			await page.locator('#hw-due-date').fill(dueDate);
			await page.getByRole('button', { name: /créer le devoir/i }).click();
			await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Titre original')).toBeVisible({ timeout: 10000 });

			// Open edit modal
			await page.getByRole('button', { name: /modifier/i }).first().click();
			const editDialog = page.getByRole('dialog');
			await expect(editDialog).toBeVisible({ timeout: 10000 });

			// Verify pre-filled values (TipTap may take time to initialize with content)
			await expect(page.locator('.modal .rich-text-content')).toBeVisible({ timeout: 10000 });
			await expect(page.locator('.modal .rich-text-content')).toContainText('Description inchangée');
			await expect(page.locator('#edit-due-date')).toHaveValue(dueDate);

			// Change only the title
			await page.locator('#edit-title').clear();
			await page.locator('#edit-title').fill('Titre mis à jour');

			await page.getByRole('button', { name: /enregistrer/i }).click();
			await expect(editDialog).not.toBeVisible({ timeout: 10000 });

			// Verify title changed
			await expect(page.getByText('Titre mis à jour')).toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Titre original')).not.toBeVisible();

			// Verify description still visible
			await expect(page.getByText('Description inchangée')).toBeVisible();
		});
	});
});
