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_PASSWORD = 'BlockingTest123';
const TARGET_PASSWORD = 'TargetUser123';

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

	// Per-browser unique emails to avoid cross-project race conditions
	// (parallel browser projects share the same database)
	let ADMIN_EMAIL: string;
	let TARGET_EMAIL: string;

	function clearCache() {
		const projectRoot = join(__dirname, '../..');
		const composeFile = join(projectRoot, 'compose.yaml');
		const pools = ['paginated_queries.cache', 'users.cache'];
		for (const pool of pools) {
			try {
				execSync(
					`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear ${pool} 2>&1`,
					{ encoding: 'utf-8' }
				);
			} catch {
				// Cache pool may not exist in all environments
			}
		}
	}

	// eslint-disable-next-line no-empty-pattern
	test.beforeAll(async ({}, testInfo) => {
		const browser = testInfo.project.name;
		ADMIN_EMAIL = `e2e-blocking-admin-${browser}@example.com`;
		TARGET_EMAIL = `e2e-blocking-target-${browser}@example.com`;

		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 target user to be blocked
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${TARGET_EMAIL} --password=${TARGET_PASSWORD} --role=ROLE_PROF 2>&1`,
			{ encoding: 'utf-8' }
		);

		// Ensure target user is unblocked before tests start (idempotent cleanup)
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "UPDATE users SET statut = 'active', blocked_at = NULL, blocked_reason = NULL WHERE email = '${TARGET_EMAIL}'" 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Ignore cleanup errors
		}

		clearCache();
	});

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

	test('admin can block a user and sees blocked status', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/users`);

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

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

		// Find the target user row
		const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
		await expect(targetRow).toBeVisible({ timeout: 10000 });

		// Click "Bloquer" button and wait for modal (retry handles hydration timing)
		await expect(async () => {
			await targetRow.getByRole('button', { name: /bloquer/i }).click();
			await expect(page.locator('#block-modal-title')).toBeVisible({ timeout: 2000 });
		}).toPass({ timeout: 10000 });

		// Fill in the reason
		await page.locator('#block-reason').fill('Comportement inapproprié en E2E');

		// Confirm the block and wait for the API response
		const blockResponsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/block') && resp.request().method() === 'POST'
		);
		await page.getByRole('button', { name: /confirmer le blocage/i }).click();
		const blockResponse = await blockResponsePromise;
		expect(blockResponse.status()).toBeLessThan(400);

		// Wait for the modal to close and status change to be reflected
		await expect(page.locator('#block-modal-title')).not.toBeVisible({ timeout: 5000 });

		// Clear cache and reload to get fresh data (block action may not invalidate paginated cache)
		clearCache();
		await page.reload();
		await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
		await page.locator('input[type="search"]').fill(TARGET_EMAIL);
		await page.waitForTimeout(1000);
		await page.waitForLoadState('networkidle');

		const updatedRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
		await expect(updatedRow).toBeVisible({ timeout: 10000 });
		await expect(updatedRow.locator('.status-blocked')).toContainText('Suspendu', { timeout: 10000 });

		// Verify the reason is displayed
		await expect(updatedRow.locator('.blocked-reason')).toContainText('Comportement inapproprié en E2E');
	});

	test('admin can unblock a suspended user', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/users`);

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

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

		// Find the suspended target user row
		const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
		await expect(targetRow).toBeVisible({ timeout: 10000 });

		// "Débloquer" button should be visible for suspended user
		const unblockButton = targetRow.getByRole('button', { name: /débloquer/i });
		await expect(unblockButton).toBeVisible();

		// Click unblock
		await unblockButton.click();

		// Wait for the success message
		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });

		// Verify the user status changed back to "Actif"
		const updatedRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
		await expect(updatedRow.locator('.status-active')).toContainText('Actif');
	});

	test('blocked user sees specific error on login', async ({ page }) => {
		// First, block the user again
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/users`);
		await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });

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

		const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
		await expect(async () => {
			await targetRow.getByRole('button', { name: /bloquer/i }).click();
			await expect(page.locator('#block-modal-title')).toBeVisible({ timeout: 2000 });
		}).toPass({ timeout: 10000 });
		await page.locator('#block-reason').fill('Bloqué pour test login');
		const blockResponsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/block') && resp.request().method() === 'POST'
		);
		await page.getByRole('button', { name: /confirmer le blocage/i }).click();
		const blockResponse = await blockResponsePromise;
		expect(blockResponse.status()).toBeLessThan(400);
		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });

		// Logout
		await page.getByRole('button', { name: /déconnexion/i }).click();

		// Try to log in as the blocked user
		await page.goto(`${ALPHA_URL}/login`);
		await page.locator('#email').fill(TARGET_EMAIL);
		await page.locator('#password').fill(TARGET_PASSWORD);
		await page.getByRole('button', { name: /se connecter/i }).click();

		// Should see a suspended account error, not the generic credentials error
		const errorBanner = page.locator('.error-banner.account-suspended');
		await expect(errorBanner).toBeVisible({ timeout: 5000 });
		await expect(errorBanner).toContainText(/suspendu|contactez/i);
	});

	test('admin cannot block themselves', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/users`);

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

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

		// Find the admin's own row
		const adminRow = page.locator('tr', { has: page.locator(`text=${ADMIN_EMAIL}`) });
		await expect(adminRow).toBeVisible({ timeout: 15000 });

		// "Bloquer" button should NOT be present on the admin's own row
		await expect(adminRow.getByRole('button', { name: /^bloquer$/i })).not.toBeVisible();
	});
});
