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 ADMIN_EMAIL = 'e2e-schedule-admin@example.com';
const ADMIN_PASSWORD = 'ScheduleTest123';
const TEACHER_EMAIL = 'e2e-schedule-teacher@example.com';
const TEACHER_PASSWORD = 'ScheduleTeacher123';
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}"; ` +
			`$dns="6ba7b810-9dad-11d1-80b4-00c04fd430c8"; ` +
			`$ay="6ba7b814-9dad-11d1-80b4-00c04fd430c8"; ` +
			`echo Ramsey\\Uuid\\Uuid::uuid5($dns,"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($ay,"$t:$s-$e")->toString();` +
			`' 2>&1`,
		{ encoding: 'utf-8' }
	).trim();
	const [schoolId, academicYearId] = output.split('\n');
	return { schoolId: schoolId!, academicYearId: academicYearId! };
}

function cleanupScheduleData() {
	try {
		runSql(`DELETE FROM schedule_slots WHERE tenant_id = '${TENANT_ID}'`);
	} catch {
		// Table may not exist yet
	}
}

function seedTeacherAssignments() {
	const { academicYearId } = resolveDeterministicIds();
	try {
		// Assign test teacher to ALL classes × ALL subjects so any dropdown combo is valid
		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
	}
}

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

async function waitForScheduleReady(page: import('@playwright/test').Page) {
	await expect(page.getByRole('heading', { name: /emploi du temps/i })).toBeVisible({
		timeout: 15000
	});
	// Wait for either the grid or the empty state to appear
	await expect(page.locator('.schedule-grid, .empty-state')).toBeVisible({
		timeout: 15000
	});
}

async function fillSlotForm(
	dialog: import('@playwright/test').Locator,
	options: {
		className?: string;
		dayValue?: string;
		startTime?: string;
		endTime?: string;
		room?: string;
	} = {}
) {
	const { className, dayValue = '1', startTime = '09:00', endTime = '10:00', room } = options;

	if (className) {
		await dialog.locator('#slot-class').selectOption({ label: className });
	}
	// Wait for assignments to load, then select subject first (filters teachers)
	const subjectOptions = dialog.locator('#slot-subject option:not([value=""])');
	await expect(subjectOptions.first()).toBeAttached({ timeout: 10000 });
	await dialog.locator('#slot-subject').selectOption({ index: 1 });
	// After subject selection, wait for teacher dropdown to be filtered
	const teacherOptions = dialog.locator('#slot-teacher option:not([value=""])');
	await expect(teacherOptions).toHaveCount(1, { timeout: 10000 });
	await dialog.locator('#slot-teacher').selectOption({ index: 1 });
	await dialog.locator('#slot-day').selectOption(dayValue);
	await dialog.locator('#slot-start').fill(startTime);
	await dialog.locator('#slot-end').fill(endTime);
	if (room) {
		await dialog.locator('#slot-room').fill(room);
	}
}

test.describe('Schedule Management - Navigation & Grid & Creation (Story 4.1)', () => {
	// Tests share database state (same tenant, users, assignments) so they must run sequentially
	test.describe.configure({ mode: 'serial' });

	test.beforeAll(async () => {
		// Create admin user
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${ADMIN_EMAIL} --password=${ADMIN_PASSWORD} --role=ROLE_ADMIN 2>&1`,
			{ encoding: 'utf-8' }
		);

		// 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' }
		);

		const { schoolId, academicYearId } = resolveDeterministicIds();

		// Clean up stale test data (e.g. from previous runs with wrong school_id)
		try {
			runSql(`DELETE FROM schedule_slots WHERE class_id IN (SELECT id FROM school_classes WHERE name IN ('E2E-Schedule-6A','E2E-Schedule-5A') AND tenant_id = '${TENANT_ID}')`);
			runSql(`DELETE FROM teacher_assignments WHERE school_class_id IN (SELECT id FROM school_classes WHERE name IN ('E2E-Schedule-6A','E2E-Schedule-5A') AND tenant_id = '${TENANT_ID}')`);
			runSql(`DELETE FROM school_classes WHERE name IN ('E2E-Schedule-6A','E2E-Schedule-5A') AND tenant_id = '${TENANT_ID}'`);
			runSql(`DELETE FROM subjects WHERE code IN ('E2SMATH','E2SFRA') AND tenant_id = '${TENANT_ID}'`);
		} catch {
			// Tables may not exist
		}

		// Create test classes
		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-Schedule-6A', '6ème', 'active', NOW(), NOW())`
		);
		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-Schedule-5A', '5ème', 'active', NOW(), NOW())`
		);

		// Create test subjects
		runSql(
			`INSERT INTO subjects (id, tenant_id, school_id, name, code, color, status, created_at, updated_at) VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-Schedule-Maths', 'E2SMATH', '#3b82f6', 'active', NOW(), NOW())`
		);
		runSql(
			`INSERT INTO subjects (id, tenant_id, school_id, name, code, color, status, created_at, updated_at) VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-Schedule-Français', 'E2SFRA', '#ef4444', 'active', NOW(), NOW())`
		);

		cleanupScheduleData();
		clearCache();
	});

	test.beforeEach(async () => {
		cleanupScheduleData();
		try {
			runSql(`DELETE FROM teacher_assignments WHERE tenant_id = '${TENANT_ID}'`);
		} catch {
			// Table may not exist
		}
		seedTeacherAssignments();
		clearCache();
	});

	// ==========================================================================
	// Navigation
	// ==========================================================================
	test.describe('Navigation', () => {
		test('schedule link appears in admin navigation under Organisation', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin`);

			const nav = page.locator('.desktop-nav');
			await nav.getByRole('button', { name: /organisation/i }).hover();
			const navLink = nav.getByRole('menuitem', { name: /emploi du temps/i });
			await expect(navLink).toBeVisible({ timeout: 15000 });
		});

		test('can navigate to schedule page', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await expect(
				page.getByRole('heading', { name: /emploi du temps/i })
			).toBeVisible({ timeout: 15000 });
		});
	});

	// ==========================================================================
	// AC1: Schedule Grid
	// ==========================================================================
	test.describe('AC1: Schedule Grid', () => {
		test('displays weekly grid with day columns', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			// Check day headers are present
			await expect(page.getByText('Lundi')).toBeVisible();
			await expect(page.getByText('Mardi')).toBeVisible();
			await expect(page.getByText('Mercredi')).toBeVisible();
			await expect(page.getByText('Jeudi')).toBeVisible();
			await expect(page.getByText('Vendredi')).toBeVisible();
		});

		test('has class filter dropdown', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			const classFilter = page.locator('#filter-class');
			await expect(classFilter).toBeVisible();
			// Should have at least the placeholder option + one class
			const options = classFilter.locator('option');
			await expect(options).not.toHaveCount(1, { timeout: 10000 });
		});

		test('has teacher filter dropdown', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			const teacherFilter = page.locator('#filter-teacher');
			await expect(teacherFilter).toBeVisible();
		});
	});

	// ==========================================================================
	// AC2: Slot Creation
	// ==========================================================================
	test.describe('AC2: Slot Creation', () => {
		test('clicking on a time cell opens creation modal', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			// Click on a time cell in the grid
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			// Modal should appear
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });
			await expect(
				dialog.getByRole('heading', { name: /nouveau créneau/i })
			).toBeVisible();
		});

		test('creation form has required fields', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			// Open creation modal
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Check required form fields
			await expect(dialog.locator('#slot-subject')).toBeVisible();
			await expect(dialog.locator('#slot-teacher')).toBeVisible();
			await expect(dialog.locator('#slot-day')).toBeVisible();
			await expect(dialog.locator('#slot-start')).toBeVisible();
			await expect(dialog.locator('#slot-end')).toBeVisible();
			await expect(dialog.locator('#slot-room')).toBeVisible();

			// Submit button should be disabled when fields are empty
			const submitButton = dialog.getByRole('button', { name: /créer/i });
			await expect(submitButton).toBeDisabled();
		});

		test('can close creation modal with cancel button', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

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

		test('can close creation modal with Escape key', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Press Escape
			await page.keyboard.press('Escape');
			await expect(dialog).not.toBeVisible({ timeout: 5000 });
		});

		test('can create a slot and see it in the grid', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);

			await waitForScheduleReady(page);

			// Open creation modal
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			await fillSlotForm(dialog, { room: 'A101' });

			// Submit
			const submitButton = dialog.getByRole('button', { name: /créer/i });
			await expect(submitButton).toBeEnabled();
			await submitButton.click();

			// Modal should close
			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Slot card should appear in the grid
			await expect(page.locator('.slot-card')).toBeVisible({ timeout: 10000 });

			// Should show room on the slot card
			await expect(page.locator('.slot-card').getByText('A101')).toBeVisible();
		});

		test('subject field appears before teacher field in creation form', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Subject should appear before teacher in DOM order
			const formGroups = dialog.locator('.form-group');
			const labels = await formGroups.locator('label').allTextContents();
			const subjectIndex = labels.findIndex((l) => l.includes('Matière'));
			const teacherIndex = labels.findIndex((l) => l.includes('Enseignant'));
			expect(subjectIndex).toBeLessThan(teacherIndex);
		});

		test('selecting a subject filters the teacher dropdown', async ({ page }) => {
			const { academicYearId } = resolveDeterministicIds();

			// Create a second teacher
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=e2e-sched-teacher2@example.com --password=Teacher2Pass123 --role=ROLE_PROF 2>&1`,
				{ encoding: 'utf-8' }
			);

			// Assign teacher1 to subject1, teacher2 to subject2 for class 6A
			runSql(`DELETE FROM teacher_assignments WHERE tenant_id = '${TENANT_ID}'`);
			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, school_classes c, (SELECT id FROM subjects WHERE code = 'E2SMATH' AND tenant_id = '${TENANT_ID}') s ` +
					`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
					`AND c.name = 'E2E-Schedule-6A' AND c.tenant_id = '${TENANT_ID}' ` +
					`ON CONFLICT DO NOTHING`
			);
			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, school_classes c, (SELECT id FROM subjects WHERE code = 'E2SFRA' AND tenant_id = '${TENANT_ID}') s ` +
					`WHERE u.email = 'e2e-sched-teacher2@example.com' AND u.tenant_id = '${TENANT_ID}' ` +
					`AND c.name = 'E2E-Schedule-6A' AND c.tenant_id = '${TENANT_ID}' ` +
					`ON CONFLICT DO NOTHING`
			);
			clearCache();

			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select class 6A — both subjects should be available
			await dialog.locator('#slot-class').selectOption({ label: 'E2E-Schedule-6A' });
			const subjectOptions = dialog.locator('#slot-subject option:not([value=""])');
			await expect(subjectOptions).toHaveCount(2, { timeout: 15000 });

			// Before selecting a subject, both teachers should be available
			const teacherOptions = dialog.locator('#slot-teacher option:not([value=""])');
			await expect(teacherOptions).toHaveCount(2, { timeout: 10000 });

			// Select first subject (Français) — should filter to only teacher2
			await dialog.locator('#slot-subject').selectOption({ index: 1 });
			await expect(teacherOptions).toHaveCount(1, { timeout: 10000 });
		});

		test('filters subjects and teachers by class assignment', async ({ page }) => {
			const { academicYearId } = resolveDeterministicIds();

			// Clear all assignments, seed exactly one: teacher → class 6A → first subject
			runSql(`DELETE FROM teacher_assignments WHERE tenant_id = '${TENANT_ID}'`);
			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, school_classes c, (SELECT id FROM subjects WHERE tenant_id = '${TENANT_ID}' ORDER BY name LIMIT 1) s ` +
					`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
					`AND c.name = 'E2E-Schedule-6A' AND c.tenant_id = '${TENANT_ID}' ` +
					`ON CONFLICT DO NOTHING`
			);
			clearCache();

			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Open creation modal
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			// Select class E2E-Schedule-6A (triggers loadAssignments for this class)
			await dialog.locator('#slot-class').selectOption({ label: 'E2E-Schedule-6A' });

			// Subject dropdown should be filtered to only the assigned subject
			// (auto-retry handles the async assignment loading)
			const subjectOptions = dialog.locator('#slot-subject option:not([value=""])');
			await expect(subjectOptions).toHaveCount(1, { timeout: 15000 });

			// Teacher dropdown should only show the assigned teacher
			const teacherOptions = dialog.locator('#slot-teacher option:not([value=""])');
			await expect(teacherOptions).toHaveCount(1, { timeout: 10000 });
		});
	});
});

test.describe('Schedule Recurring - Week Navigation & Scope (Story 4.2)', () => {
	test.describe.configure({ mode: 'serial' });

	test.beforeAll(async () => {
		// Create admin user
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${ADMIN_EMAIL} --password=${ADMIN_PASSWORD} --role=ROLE_ADMIN 2>&1`,
			{ encoding: 'utf-8' }
		);

		// 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' }
		);

		const { schoolId, academicYearId } = resolveDeterministicIds();

		// Only insert if not already created by first describe block
		try {
			runSql(
				`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) SELECT gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-Schedule-6A', '6ème', 'active', NOW(), NOW() WHERE NOT EXISTS (SELECT 1 FROM school_classes WHERE name = 'E2E-Schedule-6A' AND tenant_id = '${TENANT_ID}')`
			);
		} catch {
			// May already exist
		}

		try {
			runSql(
				`INSERT INTO subjects (id, tenant_id, school_id, name, code, color, status, created_at, updated_at) SELECT gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-Schedule-Maths', 'E2SMATH', '#3b82f6', 'active', NOW(), NOW() WHERE NOT EXISTS (SELECT 1 FROM subjects WHERE code = 'E2SMATH' AND tenant_id = '${TENANT_ID}')`
			);
		} catch {
			// May already exist
		}

		cleanupScheduleData();
		clearCache();
	});

	test.beforeEach(async () => {
		cleanupScheduleData();
		try {
			runSql(`DELETE FROM schedule_exceptions WHERE tenant_id = '${TENANT_ID}'`);
		} catch {
			// Table may not exist
		}
		try {
			runSql(`DELETE FROM teacher_assignments WHERE tenant_id = '${TENANT_ID}'`);
		} catch {
			// Table may not exist
		}
		seedTeacherAssignments();
		clearCache();
	});

	// ==========================================================================
	// AC2: Week Navigation
	// ==========================================================================
	test.describe('AC2: Week Navigation', () => {
		test('displays week navigation controls', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Week navigation should be visible
			const weekNav = page.locator('.week-nav');
			await expect(weekNav).toBeVisible();

			// Previous/next buttons
			await expect(weekNav.getByLabel('Semaine précédente')).toBeVisible();
			await expect(weekNav.getByLabel('Semaine suivante')).toBeVisible();

			// Week label
			await expect(weekNav.locator('.week-label')).toBeVisible();
		});

		test('can navigate to next and previous weeks', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			const weekLabel = page.locator('.week-label');
			const initialLabel = await weekLabel.textContent();

			// Navigate to next week
			await page.getByLabel('Semaine suivante').click();
			await expect(weekLabel).not.toHaveText(initialLabel!, { timeout: 5000 });
			const nextLabel = await weekLabel.textContent();

			// Navigate back
			await page.getByLabel('Semaine précédente').click();
			await expect(weekLabel).toHaveText(initialLabel!, { timeout: 5000 });

			// Navigate to next again
			await page.getByLabel('Semaine suivante').click();
			await expect(weekLabel).toHaveText(nextLabel!, { timeout: 5000 });
		});

		test('today button appears when not on current week', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// "Aujourd'hui" button should not be visible on current week
			await expect(page.locator('.week-nav-today')).not.toBeVisible();

			// Navigate away
			await page.getByLabel('Semaine suivante').click();

			// "Aujourd'hui" button should appear
			await expect(page.locator('.week-nav-today')).toBeVisible({ timeout: 5000 });

			// Click it to go back
			await page.locator('.week-nav-today').click();

			// Should disappear again
			await expect(page.locator('.week-nav-today')).not.toBeVisible({ timeout: 5000 });
		});
	});

	// ==========================================================================
	// AC1/AC2: Recurring indicator
	// ==========================================================================
	test.describe('AC1: Recurring Indicator', () => {
		test('recurring slots show recurring badge', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Create a slot first
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();

			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 10000 });

			await fillSlotForm(dialog, { room: 'B201' });
			await dialog.getByRole('button', { name: /créer/i }).click();
			await expect(dialog).not.toBeVisible({ timeout: 10000 });

			// Slot card should have the recurring badge
			const slotCard = page.locator('.slot-card');
			await expect(slotCard).toBeVisible({ timeout: 10000 });
			await expect(slotCard.locator('.slot-badge-recurring')).toBeVisible();
		});
	});

	// ==========================================================================
	// AC3: Scope Choice Modal
	// ==========================================================================
	test.describe('AC3: Scope Choice Modal', () => {
		test('clicking a slot opens scope choice modal', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Create a slot
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();
			const createDialog = page.getByRole('dialog');
			await expect(createDialog).toBeVisible({ timeout: 10000 });

			await fillSlotForm(createDialog, { room: 'C301' });
			await createDialog.getByRole('button', { name: /créer/i }).click();
			await expect(createDialog).not.toBeVisible({ timeout: 10000 });

			// Click on the slot card
			const slotCard = page.locator('.slot-card');
			await expect(slotCard).toBeVisible({ timeout: 10000 });
			await slotCard.click();

			// Scope modal should appear
			const scopeModal = page.locator('.modal-scope');
			await expect(scopeModal).toBeVisible({ timeout: 10000 });
			await expect(scopeModal.getByText('Cette occurrence uniquement')).toBeVisible();
			await expect(scopeModal.getByText('Toutes les occurrences futures')).toBeVisible();
		});

		test('scope modal can be closed with Escape', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Create a slot
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();
			const createDialog = page.getByRole('dialog');
			await expect(createDialog).toBeVisible({ timeout: 10000 });

			await fillSlotForm(createDialog);
			await createDialog.getByRole('button', { name: /créer/i }).click();
			await expect(createDialog).not.toBeVisible({ timeout: 10000 });

			// Click on the slot card
			const slotCard = page.locator('.slot-card');
			await expect(slotCard).toBeVisible({ timeout: 10000 });
			await slotCard.click();

			// Scope modal appears
			const scopeModal = page.locator('.modal-scope');
			await expect(scopeModal).toBeVisible({ timeout: 10000 });

			// Close with Escape
			await page.keyboard.press('Escape');
			await expect(scopeModal).not.toBeVisible({ timeout: 5000 });
		});

		test('choosing "this occurrence" opens edit form', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/schedule`);
			await waitForScheduleReady(page);

			// Create a slot
			const timeCell = page.locator('.time-cell').first();
			await timeCell.click();
			const createDialog = page.getByRole('dialog');
			await expect(createDialog).toBeVisible({ timeout: 10000 });

			await fillSlotForm(createDialog, { room: 'D401' });
			await createDialog.getByRole('button', { name: /créer/i }).click();
			await expect(createDialog).not.toBeVisible({ timeout: 10000 });

			// Click on the slot card
			const slotCard = page.locator('.slot-card');
			await expect(slotCard).toBeVisible({ timeout: 10000 });
			await slotCard.click();

			// Choose "this occurrence"
			const scopeModal = page.locator('.modal-scope');
			await expect(scopeModal).toBeVisible({ timeout: 10000 });
			await scopeModal.getByText('Cette occurrence uniquement').click();

			// Edit form should appear
			const editDialog = page.getByRole('dialog');
			await expect(editDialog).toBeVisible({ timeout: 10000 });
			await expect(
				editDialog.getByRole('heading', { name: /modifier le créneau/i })
			).toBeVisible();
		});
	});
});
