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-rules-hard-teacher@example.com';
const TEACHER_PASSWORD = 'RulesHard123';
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 weekday date string (YYYY-MM-DD), N days from now.
 * Skips weekends.
 */
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}`;
}

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

/**
 * Configure homework rules with minimum_delay of 7 days in hard mode
 * so that a homework due in 1-2 days triggers a blocking error.
 */
function seedHardRules() {
	const rulesJson = '[{\\"type\\":\\"minimum_delay\\",\\"params\\":{\\"days\\":7}}]';
	runSql(
		`INSERT INTO homework_rules (id, tenant_id, rules, enforcement_mode, enabled, created_at, updated_at) ` +
			`VALUES (gen_random_uuid(), '${TENANT_ID}', '${rulesJson}'::jsonb, 'hard', true, NOW(), NOW()) ` +
			`ON CONFLICT (tenant_id) DO UPDATE SET rules = '${rulesJson}'::jsonb, enforcement_mode = 'hard', enabled = true, updated_at = NOW()`,
	);
}



async function openCreateAndFillForm(page: import('@playwright/test').Page, title: string, daysFromNow: number) {
	await page.getByRole('button', { name: /nouveau devoir/i }).click();
	await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });

	const nearDate = getNextWeekday(daysFromNow);
	await page.locator('#hw-class').selectOption({ index: 1 });
	await expect(page.locator('#hw-subject')).toBeEnabled({ timeout: 5000 });
	await page.locator('#hw-subject').selectOption({ index: 1 });
	await page.locator('#hw-title').fill(title);
	await page.locator('#hw-due-date').fill(nearDate);

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

test.describe('Homework Rules - Hard Mode Blocking (Story 5.5)', () => {
	test.beforeAll(async () => {
		// 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();
		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-RH-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-RH-Maths', 'E2ERHM', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`,
			);
		} catch {
			// May already exist
		}

		seedTeacherAssignments();
	});

	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
		}

		// 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 */ }

		// NOTE: Do NOT call clearRules() here — it deletes rules for the shared
		// tenant and creates race conditions with other spec files running in
		// parallel. Each test seeds its own rules via seedHardRules() which uses
		// ON CONFLICT DO UPDATE, so prior state is irrelevant.
		clearCache();
	});

	// ============================================================================
	// AC1: Blocking when hard mode is active
	// ============================================================================
	test.describe('AC1: Hard mode blocks creation', () => {
		test('shows blocking modal instead of warning when hard mode is active', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir hard block', 2);

			// Blocking modal appears (not warning)
			const blockedDialog = page.getByRole('alertdialog');
			await expect(blockedDialog).toBeVisible({ timeout: 10000 });
			await expect(blockedDialog.getByText(/impossible de créer ce devoir/i)).toBeVisible();

			// No "Continuer malgré tout" button
			await expect(blockedDialog.getByRole('button', { name: /continuer malgré tout/i })).not.toBeVisible();
		});

		test('cannot bypass hard mode with acknowledgment', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir bypass test', 2);

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

			// Only "Modifier la date" button, no bypass option
			await expect(blockedDialog.getByRole('button', { name: /modifier la date/i })).toBeVisible();
			await expect(blockedDialog.getByRole('button', { name: /continuer/i })).not.toBeVisible();
		});
	});

	// ============================================================================
	// AC2: Blocking message with reason and suggestions
	// ============================================================================
	test.describe('AC2: Blocking message with suggestions', () => {
		test('shows violation reason in blocking modal', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir raison blocage', 2);

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

			// Shows the reason
			await expect(blockedDialog.getByText(/au moins/i)).toBeVisible();
			await expect(blockedDialog.getByText(/interdisent la création/i)).toBeVisible();
		});

		test('shows suggested conforming dates', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir suggestions', 2);

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

			// Suggested dates section visible
			await expect(blockedDialog.getByText(/dates conformes suggérées/i)).toBeVisible();

			// At least one suggested date button
			const suggestedGroup = blockedDialog.getByRole('group', { name: /dates conformes suggérées/i });
			await expect(suggestedGroup.getByRole('button').first()).toBeVisible();
		});

		test('clicking suggested date reopens form with that date', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir select date', 2);

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

			// Click first suggested date
			const firstSuggested = blockedDialog.getByRole('group', { name: /dates conformes suggérées/i }).getByRole('button').first();
			await firstSuggested.click();

			// Blocked modal closes, create form reopens
			await expect(blockedDialog).not.toBeVisible({ timeout: 3000 });
			const createDialog = page.getByRole('dialog');
			await expect(createDialog).toBeVisible();
			await expect(createDialog.locator('#hw-due-date')).toBeVisible();
		});
	});

	// ============================================================================
	// AC3: Exception request information
	// ============================================================================
	test.describe('AC3: Exception request information', () => {
		test('shows exception request button in blocking modal', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir exception link', 2);

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

			// Exception request button visible (Story 5.6 replaced static text with a real button)
			await expect(blockedDialog.getByRole('button', { name: /demander une exception/i })).toBeVisible();
		});
	});

	// ============================================================================
	// AC4: Calendar with conforming dates
	// ============================================================================
	test.describe('AC4: Calendar enforces conforming dates', () => {
		test('modify date reopens form with conforming minimum date', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir modify date hard', 2);

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

			// Click "Modifier la date"
			await blockedDialog.getByRole('button', { name: /modifier la date/i }).click();

			// Form reopens
			await expect(blockedDialog).not.toBeVisible({ timeout: 3000 });
			const createDialog = page.getByRole('dialog');
			await expect(createDialog).toBeVisible();

			// Date input should have min attribute enforcing conforming dates
			const dateInput = createDialog.locator('#hw-due-date');
			await expect(dateInput).toBeVisible();
			const minValue = await dateInput.getAttribute('min');
			expect(minValue).toBeTruthy();
		});

		test('homework can be created with compliant date after modification', async ({ page }) => {
			seedHardRules();
			clearCache();

			await loginAsTeacher(page);
			await navigateToHomework(page);
			await openCreateAndFillForm(page, 'Devoir compliant hard', 2);

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

			// Click first suggested date
			const firstSuggested = blockedDialog.getByRole('group', { name: /dates conformes suggérées/i }).getByRole('button').first();
			await firstSuggested.click();

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

			// Homework created successfully
			await expect(page.getByText('Devoir compliant hard')).toBeVisible({ timeout: 10000 });
		});
	});
});
