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

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

// Réutilise le même enseignant que homework.spec.ts pour partager le setup
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! };
}

function getNextWeekday(daysFromNow: number): string {
	const date = new Date();
	date.setDate(date.getDate() + daysFromNow);
	const day = date.getDay();
	if (day === 0) date.setDate(date.getDate() + 1);
	if (day === 6) date.setDate(date.getDate() + 2);
	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}`;
}

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 {
		// May already exist
	}
}

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

async function createHomework(page: import('@playwright/test').Page, title: string) {
	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(title);

	// Type in WYSIWYG editor (TipTap initializes asynchronously)
	const editorContent = page.locator('.modal .rich-text-content');
	await expect(editorContent).toBeVisible({ timeout: 10000 });
	await editorContent.click();
	await page.keyboard.type('Consignes du devoir');

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

function createTempPdf(): string {
	const tmpDir = join(__dirname, '..', 'tmp-test-files');
	mkdirSync(tmpDir, { recursive: true });
	const filePath = join(tmpDir, 'test-attachment.pdf');
	const pdfContent = `%PDF-1.4
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj
2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj
3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]>>endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer<</Size 4/Root 1 0 R>>
startxref
190
%%EOF`;
	writeFileSync(filePath, pdfContent);
	return filePath;
}

function createTempTxt(): string {
	const tmpDir = join(__dirname, '..', 'tmp-test-files');
	mkdirSync(tmpDir, { recursive: true });
	const filePath = join(tmpDir, 'test-invalid.txt');
	writeFileSync(filePath, 'This is a plain text file that should be rejected.');
	return filePath;
}

function cleanupTempFiles() {
	const tmpDir = join(__dirname, '..', 'tmp-test-files');
	for (const name of ['test-attachment.pdf', 'test-invalid.txt']) {
		try {
			unlinkSync(join(tmpDir, name));
		} catch {
			// May not exist
		}
	}
}

test.describe('Rich Text & Attachments (Story 5.9)', () => {
	test.beforeAll(async () => {
		// Ensure teacher user exists (same as homework.spec.ts)
		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();
		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 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.afterAll(() => {
		cleanupTempFiles();
	});

	test.beforeEach(async () => {
		// homework_submissions has NO CASCADE on homework_id — 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 blocking homework creation.
		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 */ }

		clearCache();
	});

	// ============================================================================
	// T4.1 : WYSIWYG Editor
	// ============================================================================
	test.describe('WYSIWYG Editor', () => {
		test('create form shows rich text editor with toolbar', 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 });

			// Rich text editor with toolbar should be visible
			const editor = page.locator('.rich-text-editor');
			await expect(editor).toBeVisible({ timeout: 5000 });
			await expect(page.locator('.toolbar')).toBeVisible();

			// Toolbar buttons
			await expect(page.getByRole('button', { name: 'Gras' })).toBeVisible();
			await expect(page.getByRole('button', { name: 'Italique' })).toBeVisible();
			await expect(page.getByRole('button', { name: 'Liste à puces' })).toBeVisible();
			await expect(page.getByRole('button', { name: 'Liste numérotée' })).toBeVisible();
			await expect(page.getByRole('button', { name: 'Lien' })).toBeVisible();
		});

		test('can create homework with rich text description', 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 texte riche');

			// Type in rich text editor (TipTap initializes asynchronously)
			const editorContent = page.locator('.modal .rich-text-content');
			await expect(editorContent).toBeVisible({ timeout: 10000 });
			await editorContent.click();
			await page.keyboard.type('Consignes importantes');

			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 texte riche')).toBeVisible({ timeout: 10000 });
		});

		test('bold formatting works in editor', 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 gras test');

			const editorContent = page.locator('.modal .rich-text-content');
			await expect(editorContent).toBeVisible({ timeout: 10000 });
			await editorContent.click();
			await page.keyboard.type('Normal ');

			// Apply bold via keyboard shortcut (more reliable than toolbar click)
			await page.keyboard.press('Control+b');
			await page.keyboard.type('en gras');

			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 gras test')).toBeVisible({ timeout: 10000 });

			// Verify bold is rendered in the description
			const description = page.locator('.homework-description');
			await expect(description.locator('strong')).toContainText('en gras');
		});
	});

	// ============================================================================
	// T4.2 : Upload attachment
	// ============================================================================
	test.describe('Attachments', () => {
		test('can upload a PDF attachment to homework via edit modal', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Create homework
			await createHomework(page, 'Devoir avec PJ');

			// Open edit modal
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir avec PJ' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Upload file
			const pdfPath = createTempPdf();
			const fileInput = page.locator('.file-input-hidden');
			await fileInput.setInputFiles(pdfPath);

			// File appears in list
			await expect(page.getByText('test-attachment.pdf')).toBeVisible({ timeout: 15000 });
		});

		// T4.3 : Delete attachment
		test('can delete an uploaded attachment', async ({ page }) => {
			test.slow(); // upload + delete needs more than 30s
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await createHomework(page, 'Devoir suppr PJ');

			// Open edit modal and upload
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir suppr PJ' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			const pdfPath = createTempPdf();
			await page.locator('.file-input-hidden').setInputFiles(pdfPath);
			await expect(page.getByText('test-attachment.pdf')).toBeVisible({ timeout: 15000 });

			// Delete the attachment
			await page.getByRole('button', { name: /supprimer test-attachment.pdf/i }).click();
			await expect(page.getByText('test-attachment.pdf')).not.toBeVisible({ timeout: 5000 });
		});
	});

	// ============================================================================
	// T5.9.1 : Invalid file type rejection (P1)
	// ============================================================================
	test.describe('Invalid File Type Rejection', () => {
		test('rejects a .txt file with an error message', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await createHomework(page, 'Devoir rejet fichier');

			// Open edit modal
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir rejet fichier' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Try to upload a .txt file
			const txtPath = createTempTxt();
			const fileInput = page.locator('.file-input-hidden');
			await fileInput.setInputFiles(txtPath);

			// Error message should appear
			const errorAlert = page.locator('[role="alert"]');
			await expect(errorAlert).toBeVisible({ timeout: 5000 });
			await expect(errorAlert).toContainText('Type de fichier non accepté');

			// The .txt file should NOT appear in the file list
			await expect(page.getByText('test-invalid.txt')).not.toBeVisible();
		});
	});

	// ============================================================================
	// T5.9.2 : Attachment persistence after save (P1)
	// ============================================================================
	test.describe('Attachment Persistence', () => {
		test('uploaded attachment persists after saving and reopening edit modal', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await createHomework(page, 'Devoir persistance PJ');

			// Open edit modal
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir persistance PJ' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Upload a PDF
			const pdfPath = createTempPdf();
			const fileInput = page.locator('.file-input-hidden');
			await fileInput.setInputFiles(pdfPath);
			await expect(page.getByText('test-attachment.pdf')).toBeVisible({ timeout: 15000 });

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

			// Reopen the edit modal
			const hwCardAfterSave = page.locator('.homework-card', { hasText: 'Devoir persistance PJ' });
			await hwCardAfterSave.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// The attachment should still be there
			await expect(page.getByText('test-attachment.pdf')).toBeVisible({ timeout: 15000 });
		});
	});

	// ============================================================================
	// T5.9.3 : File size display after upload (P2)
	// ============================================================================
	test.describe('File Size Display', () => {
		test('shows formatted file size after uploading a PDF', async ({ page }) => {
			await loginAsTeacher(page);
			await navigateToHomework(page);

			await createHomework(page, 'Devoir taille fichier');

			// Open edit modal
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir taille fichier' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// Upload a PDF
			const pdfPath = createTempPdf();
			const fileInput = page.locator('.file-input-hidden');
			await fileInput.setInputFiles(pdfPath);
			await expect(page.getByText('test-attachment.pdf')).toBeVisible({ timeout: 15000 });

			// The file size element should be visible and show a formatted size (e.g., "xxx o" or "xxx Ko")
			const fileSize = page.locator('.file-size');
			await expect(fileSize).toBeVisible({ timeout: 5000 });
			await expect(fileSize).toHaveText(/\d+(\.\d+)?\s*(o|Ko|Mo)/);
		});
	});

	// ============================================================================
	// T4.4 : Backward compatibility
	// ============================================================================
	test.describe('Backward Compatibility', () => {
		test('existing plain text homework displays correctly', async ({ page }) => {
			// Create homework with plain text description via SQL
			runSql(
				`INSERT INTO homework (id, tenant_id, class_id, subject_id, teacher_id, title, description, due_date, status, created_at, updated_at) ` +
					`SELECT gen_random_uuid(), '${TENANT_ID}', c.id, s.id, u.id, 'Devoir texte brut E2E', 'Description simple sans balise HTML', '${getNextWeekday(10)}', 'published', NOW(), NOW() ` +
					`FROM users u, school_classes c, subjects s ` +
					`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
					`AND c.tenant_id = '${TENANT_ID}' AND c.name = 'E2E-HW-6A' ` +
					`AND s.tenant_id = '${TENANT_ID}' AND s.name = 'E2E-HW-Maths' ` +
					`LIMIT 1`
			);
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Plain text description displays correctly
			await expect(page.getByText('Devoir texte brut E2E')).toBeVisible({ timeout: 10000 });
			await expect(page.getByText('Description simple sans balise HTML')).toBeVisible();
		});

		test('edit modal loads plain text in WYSIWYG editor', async ({ page }) => {
			runSql(
				`INSERT INTO homework (id, tenant_id, class_id, subject_id, teacher_id, title, description, due_date, status, created_at, updated_at) ` +
					`SELECT gen_random_uuid(), '${TENANT_ID}', c.id, s.id, u.id, 'Devoir edit brut E2E', 'Ancienne description', '${getNextWeekday(10)}', 'published', NOW(), NOW() ` +
					`FROM users u, school_classes c, subjects s ` +
					`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
					`AND c.tenant_id = '${TENANT_ID}' AND c.name = 'E2E-HW-6A' ` +
					`AND s.tenant_id = '${TENANT_ID}' AND s.name = 'E2E-HW-Maths' ` +
					`LIMIT 1`
			);
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);

			// Open edit modal
			const hwCard = page.locator('.homework-card', { hasText: 'Devoir edit brut E2E' });
			await hwCard.getByRole('button', { name: /modifier/i }).click();
			await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });

			// WYSIWYG editor contains the old text (TipTap initializes asynchronously)
			const editorContent = page.locator('.modal .rich-text-content');
			await expect(editorContent).toBeVisible({ timeout: 10000 });
			await expect(editorContent).toContainText('Ancienne description', { timeout: 5000 });
		});
	});
});
