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}`;

// Test credentials
const ADMIN_EMAIL = 'e2e-guardian-admin@example.com';
const ADMIN_PASSWORD = 'GuardianTest123';
const STUDENT_EMAIL = 'e2e-guardian-student@example.com';
const STUDENT_PASSWORD = 'StudentTest123';
const PARENT_EMAIL = 'e2e-guardian-parent@example.com';
const PARENT_PASSWORD = 'ParentTest123';
const PARENT_FIRST_NAME = 'GuardParent';
const PARENT_LAST_NAME = 'TestOne';
const PARENT2_EMAIL = 'e2e-guardian-parent2@example.com';
const PARENT2_PASSWORD = 'Parent2Test123';
const PARENT2_FIRST_NAME = 'GuardParent';
const PARENT2_LAST_NAME = 'TestTwo';

let studentUserId: 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('Guardian 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 first parent user (with name for search-based linking)
		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 --firstName=${PARENT_FIRST_NAME} --lastName=${PARENT_LAST_NAME} 2>&1`,
			{ encoding: 'utf-8' }
		);

		// Create second parent user for the max guardians test
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${PARENT2_EMAIL} --password=${PARENT2_PASSWORD} --role=ROLE_PARENT --firstName=${PARENT2_FIRST_NAME} --lastName=${PARENT2_LAST_NAME} 2>&1`,
			{ encoding: 'utf-8' }
		);

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

	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: 60000 }),
			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: 10000 });
		await expect(
			page.getByText(/aucun parent\/tuteur lié/i)
				.or(page.locator('.guardian-list'))
		).toBeVisible({ timeout: 10000 });
	}

	/**
	 * Opens the add-guardian dialog, searches for a parent by name,
	 * selects them from results, and submits.
	 * Waits for the success message before returning.
	 */
	async function addGuardianViaDialog(
		page: import('@playwright/test').Page,
		parentSearchTerm: string,
		relationshipType: string
	) {
		await page.getByRole('button', { name: /ajouter un parent/i }).click();

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

		// Type in search field and wait for results
		const searchInput = dialog.getByRole('combobox', { name: /rechercher/i });
		await searchInput.fill(parentSearchTerm);

		// Wait for autocomplete results
		const listbox = dialog.locator('#parent-search-listbox');
		await expect(listbox).toBeVisible({ timeout: 10000 });
		const option = listbox.locator('[role="option"]').first();
		await option.click();

		// Verify a parent was selected
		await expect(dialog.getByText(/sélectionné/i)).toBeVisible();

		await dialog.getByLabel(/type de relation/i).selectOption(relationshipType);
		await dialog.getByRole('button', { name: 'Ajouter' }).click();

		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
	}

	/**
	 * Removes the first guardian in the list using the two-step confirmation.
	 * Waits for the success message before returning.
	 */
	async function removeFirstGuardian(page: import('@playwright/test').Page) {
		const guardianItem = page.locator('.guardian-item').first();
		await expect(guardianItem).toBeVisible();
		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 });
	}

	test('[P1] should display empty guardian list for student with no guardians', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

		await waitForGuardianSection(page);

		// Should show the empty state since no guardians are linked
		await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible();

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

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

		await waitForGuardianSection(page);

		// Add the guardian via the dialog (search by email)
		await addGuardianViaDialog(page, PARENT_EMAIL, 'père');

		// Verify success message
		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('[P1] should not show already-linked parent in search results', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

		await waitForGuardianSection(page);

		// The parent from the previous test is still linked
		await expect(page.locator('.guardian-list')).toBeVisible({ timeout: 5000 });

		// Open the add-guardian dialog and search for the already-linked parent
		await page.getByRole('button', { name: /ajouter un parent/i }).click();

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

		const searchInput = dialog.getByRole('combobox', { name: /rechercher/i });
		await searchInput.fill(PARENT_EMAIL);

		// The listbox should show "Aucun parent trouvé" since the parent is excluded
		const listbox = dialog.locator('#parent-search-listbox');
		await expect(listbox).toBeVisible({ timeout: 10000 });
		await expect(listbox.getByText(/aucun parent trouvé/i)).toBeVisible();

		// No option should be selectable
		await expect(listbox.locator('[role="option"]')).toHaveCount(0);

		// The guardian count should still be 1
		await dialog.locator('.modal-close').click();
		await expect(page.locator('.guardian-item')).toHaveCount(1);
	});

	test('[P1] should 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 });

		// Remove the first guardian using the two-step confirmation
		await removeFirstGuardian(page);

		// Verify success message
		await expect(page.locator('.alert-success')).toContainText(/liaison supprimée/i);

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

	test('[P2] should not show add button when maximum guardians reached', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

		await waitForGuardianSection(page);

		// Link first guardian (père) — search by email
		await addGuardianViaDialog(page, PARENT_EMAIL, 'père');

		// Wait for the add button to still be available after first link
		await expect(page.getByRole('button', { name: /ajouter un parent/i })).toBeVisible({ timeout: 5000 });

		// Link second guardian (mère) — search by email
		await addGuardianViaDialog(page, PARENT2_EMAIL, 'mère');

		// Now with 2 guardians linked, the add button should NOT be visible
		await expect(page.getByRole('button', { name: /ajouter un parent/i })).not.toBeVisible({ timeout: 5000 });

		// Verify both guardian items are displayed
		await expect(page.locator('.guardian-item')).toHaveCount(2);

		// Clean up: remove both guardians so the state is clean for potential re-runs
		await removeFirstGuardian(page);
		await expect(page.locator('.guardian-item')).toHaveCount(1, { timeout: 5000 });
		await removeFirstGuardian(page);

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

	test('[P1] should clear search field when reopening add dialog after successful link', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);

		await waitForGuardianSection(page);

		// Link a guardian via dialog (this fills the search, selects, and submits)
		await addGuardianViaDialog(page, PARENT_EMAIL, 'père');

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

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

		// The search field should be empty (reset after successful link)
		await expect(dialog.getByRole('combobox', { name: /rechercher/i })).toHaveValue('');

		// No parent should be marked as selected
		await expect(dialog.getByText(/sélectionné/i)).not.toBeVisible();

		// Close modal and clean up
		await dialog.locator('.modal-close').click();
		await removeFirstGuardian(page);
		await expect(page.getByText(/aucun parent\/tuteur lié/i)).toBeVisible({ timeout: 5000 });
	});
});
