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);

// Extract port from PLAYWRIGHT_BASE_URL or use default (4173 matches playwright.config.ts)
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}`;

// Test credentials
const ADMIN_EMAIL = 'e2e-students-admin@example.com';
const ADMIN_PASSWORD = 'StudentsTest123';
const STUDENT_EMAIL = 'e2e-students-eleve@example.com';
const STUDENT_PASSWORD = 'StudentTest123';
const PARENT_EMAIL = 'e2e-students-parent@example.com';
const PARENT_PASSWORD = 'ParentTest123';

let studentUserId: string;
let parentUserId: string;

/**
 * Extracts the User ID from the Symfony console table output.
 *
 * The create-test-user command outputs a table like:
 *   | Property | Value                                |
 *   | User ID  | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
 */
function extractUserId(output: string): string {
	const match = output.match(/User ID\s+([a-f0-9-]{36})/i);
	if (!match) {
		throw new Error(`Could not extract User ID from command output:\n${output}`);
	}
	return match[1];
}

test.describe('Student Management', () => {
	test.describe.configure({ mode: 'serial' });

	test.beforeAll(async () => {
		const projectRoot = join(__dirname, '../..');
		const composeFile = join(projectRoot, 'compose.yaml');

		// 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 student user and capture userId
		const studentOutput = execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${STUDENT_EMAIL} --password=${STUDENT_PASSWORD} --role=ROLE_ELEVE 2>&1`,
			{ encoding: 'utf-8' }
		);
		studentUserId = extractUserId(studentOutput);

		// Create parent user and capture userId
		const parentOutput = execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${PARENT_EMAIL} --password=${PARENT_PASSWORD} --role=ROLE_PARENT 2>&1`,
			{ encoding: 'utf-8' }
		);
		parentUserId = extractUserId(parentOutput);

		// Clean up any existing guardian links for this student (DB + cache)
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "DELETE FROM student_guardians WHERE student_id = '${studentUserId}'" 2>&1`,
				{ encoding: 'utf-8' }
			);
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear student_guardians.cache --env=dev 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Ignore cleanup errors -- table may not have data yet
		}
	});

	// Helper to login as admin
	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()
		]);
	}

	/**
	 * Waits for the guardian section to be fully hydrated (client-side JS loaded).
	 *
	 * The server renders the section with a "Chargement..." indicator. Only after
	 * client-side hydration does the $effect() fire, triggering loadGuardians().
	 * When that completes, either the empty-state or the guardian-list appears.
	 * Waiting for one of these ensures the component is interactive.
	 */
	async function waitForGuardianSection(page: import('@playwright/test').Page) {
		await expect(page.locator('.guardian-section')).toBeVisible({ timeout: 15000 });
		await expect(
			page.getByText(/aucun parent\/tuteur lié/i)
				.or(page.locator('.guardian-list'))
		).toBeVisible({ timeout: 15000 });
	}

	// ============================================================================
	// Student Detail Page - Navigation
	// ============================================================================
	test.describe('Navigation', () => {
		test('can access student detail page via direct URL', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			// Page should load with the student detail heading
			await expect(page.getByRole('heading', { name: /fiche élève/i })).toBeVisible({ timeout: 10000 });
		});

		test('page title is set correctly', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await expect(page).toHaveTitle(/fiche élève/i);
		});

		test('back link navigates to students page', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			// Wait for page to be fully loaded
			await expect(page.getByRole('heading', { name: /fiche élève/i })).toBeVisible({ timeout: 10000 });

			// Click the back link
			await page.locator('.back-link').click();

			// Should navigate to students page
			await expect(page).toHaveURL(/\/admin\/students/);
		});
	});

	// ============================================================================
	// Student Detail Page - Guardian Section
	// ============================================================================
	test.describe('Guardian Section', () => {
		test('shows empty guardian list for student with no guardians', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Clean up guardians if any exist (cross-browser interference: parallel
			// browser projects share the same DB, so another browser may have added
			// guardians between our beforeAll cleanup and this test).
			while (await page.locator('.guardian-item').count() > 0) {
				const item = page.locator('.guardian-item').first();
				await item.getByRole('button', { name: /retirer/i }).click();
				await expect(item.getByText(/confirmer/i)).toBeVisible({ timeout: 5000 });
				await item.getByRole('button', { name: /oui/i }).click();
				await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
				await page.waitForTimeout(500);
			}

			// Should show the empty state
			await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible({ timeout: 15000 });

			// The "add guardian" button should be visible
			await expect(page.getByRole('button', { name: /ajouter un parent/i })).toBeVisible();
		});

		test('displays the guardian section header', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Section title should be visible
			await expect(page.getByRole('heading', { name: /parents \/ tuteurs/i })).toBeVisible();
		});

		test('can open add guardian modal', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Click the add guardian button
			await page.getByRole('button', { name: /ajouter un parent/i }).click();

			// Modal should appear
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 5000 });

			// Modal should have the correct heading
			await expect(dialog.getByRole('heading', { name: /ajouter un parent\/tuteur/i })).toBeVisible();

			// Form fields should be present
			await expect(dialog.getByLabel(/id du parent/i)).toBeVisible();
			await expect(dialog.getByLabel(/type de relation/i)).toBeVisible();
		});

		test('can cancel adding a guardian', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Open the modal
			await page.getByRole('button', { name: /ajouter un parent/i }).click();

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

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

			// Modal should close
			await expect(dialog).not.toBeVisible();

			// Empty state should remain
			await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible();
		});

		test('can link a guardian to a student', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Open the add guardian modal
			await page.getByRole('button', { name: /ajouter un parent/i }).click();

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

			// Fill in the guardian details
			await dialog.getByLabel(/id du parent/i).fill(parentUserId);
			await dialog.getByLabel(/type de relation/i).selectOption('père');

			// Submit
			await dialog.getByRole('button', { name: 'Ajouter' }).click();

			// Success message should appear
			await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
			await expect(page.locator('.alert-success')).toContainText(/parent ajouté/i);

			// The guardian list should now contain the new item
			await expect(page.locator('.guardian-list')).toBeVisible({ timeout: 5000 });
			const guardianItem = page.locator('.guardian-item').first();
			await expect(guardianItem).toBeVisible();
			await expect(guardianItem).toContainText('Père');

			// Empty state should no longer be visible
			await expect(page.getByText(/aucun parent\/tuteur lié/i)).not.toBeVisible();
		});

		test('can unlink a guardian from a student', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Wait for the guardian list to be loaded (from previous test)
			await expect(page.locator('.guardian-list')).toBeVisible({ timeout: 5000 });

			// Click remove on the first guardian
			const guardianItem = page.locator('.guardian-item').first();
			await expect(guardianItem).toBeVisible();
			await guardianItem.getByRole('button', { name: /retirer/i }).click();

			// Two-step confirmation should appear
			await expect(guardianItem.getByText(/confirmer/i)).toBeVisible({ timeout: 5000 });
			await guardianItem.getByRole('button', { name: /oui/i }).click();

			// Success message should appear
			await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
			await expect(page.locator('.alert-success')).toContainText(/liaison supprimée/i);

			// The empty state should return
			await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible({ timeout: 5000 });
		});

		test('can cancel guardian removal', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// First, add a guardian to have something to remove
			await page.getByRole('button', { name: /ajouter un parent/i }).click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 5000 });
			await dialog.getByLabel(/id du parent/i).fill(parentUserId);
			await dialog.getByLabel(/type de relation/i).selectOption('mère');
			await dialog.getByRole('button', { name: 'Ajouter' }).click();
			await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });

			// Now try to remove but cancel
			const guardianItem = page.locator('.guardian-item').first();
			await expect(guardianItem).toBeVisible();
			await guardianItem.getByRole('button', { name: /retirer/i }).click();

			// Confirmation should appear
			await expect(guardianItem.getByText(/confirmer/i)).toBeVisible({ timeout: 5000 });

			// Cancel the removal
			await guardianItem.getByRole('button', { name: /non/i }).click();

			// Guardian should still be in the list
			await expect(page.locator('.guardian-item')).toHaveCount(1);
		});

		test('relationship type options are available in the modal', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

			await waitForGuardianSection(page);

			// Open the add guardian modal
			await page.getByRole('button', { name: /ajouter un parent/i }).click();
			const dialog = page.getByRole('dialog');
			await expect(dialog).toBeVisible({ timeout: 5000 });

			// Verify all relationship options are available
			const select = dialog.getByLabel(/type de relation/i);
			const options = select.locator('option');

			// Count options (should include: père, mère, tuteur, tutrice, grand-père, grand-mère, autre)
			const count = await options.count();
			expect(count).toBeGreaterThanOrEqual(7);

			// Verify some specific options exist (use exact match to avoid substring matches like Grand-père)
			await expect(options.filter({ hasText: /^Père$/ })).toHaveCount(1);
			await expect(options.filter({ hasText: /^Mère$/ })).toHaveCount(1);
			await expect(options.filter({ hasText: /^Tuteur$/ })).toHaveCount(1);

			// Close modal
			await dialog.getByRole('button', { name: /annuler/i }).click();
		});
	});

	// ============================================================================
	// Student Detail Page - Access from Users Page
	// ============================================================================
	test.describe('Access from Users Page', () => {
		test('users page lists the student user', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/users`);

			// Wait for users table to load
			await expect(
				page.locator('.users-table, .empty-state')
			).toBeVisible({ timeout: 10000 });

			// Search for the student user (pagination may hide them beyond page 1)
			const searchInput = page.locator('input[type="search"]');
			await searchInput.fill(STUDENT_EMAIL);
			await page.waitForTimeout(500);
			await page.waitForLoadState('networkidle');

			// The student email should appear in the users table
			await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
			const studentRow = page.locator('tr', { has: page.locator(`text=${STUDENT_EMAIL}`) });
			await expect(studentRow).toBeVisible();
		});

		test('users table shows student role', async ({ page }) => {
			await loginAsAdmin(page);
			await page.goto(`${ALPHA_URL}/admin/users`);

			// Wait for users table
			await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });

			// Search for the student user (pagination may hide them beyond page 1)
			const searchInput = page.locator('input[type="search"]');
			await searchInput.fill(STUDENT_EMAIL);
			await page.waitForTimeout(500);
			await page.waitForLoadState('networkidle');

			// Find the student row and verify role
			const studentRow = page.locator('tr', { has: page.locator(`text=${STUDENT_EMAIL}`) });
			await expect(studentRow).toContainText(/élève/i);
		});
	});

	// ============================================================================
	// Cleanup - remove guardian links after all tests
	// ============================================================================
	test('cleanup: remove remaining guardian links', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

		await waitForGuardianSection(page);

		// Remove all remaining guardians
		while (await page.locator('.guardian-item').count() > 0) {
			const guardianItem = page.locator('.guardian-item').first();
			await guardianItem.getByRole('button', { name: /retirer/i }).click();
			await expect(guardianItem.getByText(/confirmer/i)).toBeVisible({ timeout: 5000 });
			await guardianItem.getByRole('button', { name: /oui/i }).click();
			await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
			// Wait for the list to update
			await page.waitForTimeout(500);
		}

		// Verify empty state
		await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible({ timeout: 5000 });
	});
});
