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 — unique to this spec to avoid cross-spec collisions
const ADMIN_EMAIL = 'e2e-branding-admin@example.com';
const ADMIN_PASSWORD = 'BrandingAdmin123';
const TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

// Minimal valid 1x1 transparent PNG for logo upload tests
const TEST_LOGO_PNG = Buffer.from(
	'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
	'base64'
);

test.describe('Branding Visual Customization', () => {
	test.describe.configure({ mode: 'serial' });

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

		// Clear rate limiter to prevent login throttling across serial tests
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Cache pool may not exist
		}

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

		// Clean up branding data from previous test runs
		execSync(
			`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "DELETE FROM school_branding WHERE tenant_id = '${TENANT_ID}'" 2>&1`,
			{ encoding: 'utf-8' }
		);

		// Clean up logo files from previous test runs
		execSync(
			`docker compose -f "${composeFile}" exec -T php sh -c "rm -rf /app/public/uploads/logos/${TENANT_ID}" 2>&1`,
			{ encoding: 'utf-8' }
		);

		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 in all environments
		}
	});

	test.beforeEach(async () => {
		const projectRoot = join(__dirname, '../..');
		const composeFile = join(projectRoot, 'compose.yaml');
		try {
			execSync(
				`docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`,
				{ encoding: 'utf-8' }
			);
		} catch {
			// Cache pool may not exist
		}
	});

	// 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: 60000 }),
			page.getByRole('button', { name: /se connecter/i }).click()
		]);
	}

	/**
	 * Waits for the branding page to finish loading.
	 *
	 * After hydration, the page shows the card sections (logo + colors).
	 * Waiting for the heading and the first .card ensures the component
	 * is interactive and API data has been fetched.
	 */
	async function waitForPageLoaded(page: import('@playwright/test').Page) {
		await expect(
			page.getByRole('heading', { name: /identité visuelle/i })
		).toBeVisible({ timeout: 15000 });

		// Wait for at least one card section to appear (loading finished)
		await expect(
			page.locator('.card').first()
		).toBeVisible({ timeout: 15000 });
	}

	// ============================================================================
	// [P2] Page displays logo and color sections (AC1)
	// ============================================================================
	test('[P2] page affiche les sections logo et couleurs', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Title
		await expect(
			page.getByRole('heading', { name: /identité visuelle/i })
		).toBeVisible();

		// Subtitle
		await expect(
			page.getByText(/personnalisez le logo et les couleurs/i)
		).toBeVisible();

		// Logo section heading
		await expect(
			page.getByRole('heading', { name: /logo de l'établissement/i })
		).toBeVisible();

		// Format info
		await expect(
			page.getByText(/formats acceptés/i)
		).toBeVisible();

		// Logo placeholder (no logo initially)
		await expect(
			page.getByText(/aucun logo configuré/i)
		).toBeVisible();

		// Upload button
		await expect(
			page.getByText('Importer un logo')
		).toBeVisible();

		// Color section heading
		await expect(
			page.getByRole('heading', { name: /couleur principale/i })
		).toBeVisible();

		// Color picker and text input
		await expect(page.locator('#primaryColorPicker')).toBeVisible();
		await expect(page.locator('#primaryColor')).toBeVisible();

		// Reset and save buttons
		await expect(
			page.getByRole('button', { name: /réinitialiser/i })
		).toBeVisible();
		await expect(
			page.getByRole('button', { name: /enregistrer/i })
		).toBeVisible();
	});

	// ============================================================================
	// [P1] Changing color updates contrast indicator and preview (AC3)
	// ============================================================================
	test('[P1] modifier la couleur met à jour l\'indicateur de contraste et l\'aperçu', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Use color picker input for reliable cross-browser reactive updates
		// Wrap in toPass() to handle Firefox timing where fill() may not immediately trigger change event
		const colorPicker = page.locator('#primaryColorPicker');

		// --- Dark blue: passes AA (ratio ~10.3) → "Lisible" ---
		await expect(async () => {
			await colorPicker.fill('#1e3a5f');
			await expect(page.locator('.contrast-badge')).toContainText('Lisible', { timeout: 2000 });
		}).toPass({ timeout: 10000 });
		await expect(page.locator('.contrast-indicator.pass')).toBeVisible();
		await expect(page.locator('.preview-swatch').first()).toBeVisible();
		await expect(page.locator('.preview-swatch').first()).toHaveCSS(
			'background-color',
			'rgb(30, 58, 95)',
			{ timeout: 5000 }
		);

		// --- Yellow: fails AA completely (ratio ~1.07) → "Illisible" ---
		await expect(async () => {
			await colorPicker.fill('#ffff00');
			await expect(page.locator('.contrast-badge')).toContainText('Illisible', { timeout: 2000 });
		}).toPass({ timeout: 10000 });
		await expect(page.locator('.contrast-indicator.fail')).toBeVisible();

		// --- Dark yellow: passes AA Large only (ratio ~3.7) → "Attention" ---
		await expect(async () => {
			await colorPicker.fill('#8b8000');
			await expect(page.locator('.contrast-badge')).toContainText('Attention', { timeout: 2000 });
		}).toPass({ timeout: 10000 });
		await expect(page.locator('.contrast-indicator.warning')).toBeVisible();
	});

	// ============================================================================
	// [P1] Saving colors applies CSS variables immediately (AC3, AC5)
	// ============================================================================
	test('[P1] enregistrer les couleurs applique les CSS variables immédiatement', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Set a dark blue color via color picker (more reliable than text input across browsers)
		// Wrap in toPass() to handle Firefox timing where fill() may not immediately trigger change event
		await expect(async () => {
			await page.locator('#primaryColorPicker').fill('#1e3a5f');
			await expect(page.getByRole('button', { name: /enregistrer/i })).toBeEnabled({ timeout: 2000 });
		}).toPass({ timeout: 10000 });

		// Click save and wait for API response
		const responsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/school/branding') && resp.request().method() === 'PUT'
		);
		await page.getByRole('button', { name: /enregistrer/i }).click();
		await responsePromise;

		// Success message
		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 15000 });
		await expect(page.locator('.alert-success')).toContainText(/couleurs mises à jour/i);

		// CSS variables applied to document root
		const accentPrimary = await page.evaluate(() =>
			document.documentElement.style.getPropertyValue('--accent-primary')
		);
		expect(accentPrimary).toBe('#1E3A5F');

		const btnPrimaryBg = await page.evaluate(() =>
			document.documentElement.style.getPropertyValue('--btn-primary-bg')
		);
		expect(btnPrimaryBg).toBe('#1E3A5F');

		// Save button should be disabled (no pending changes)
		await expect(
			page.getByRole('button', { name: /enregistrer/i })
		).toBeDisabled();
	});

	// ============================================================================
	// [P2] Upload logo displays preview (AC2)
	// ============================================================================
	test('[P2] upload logo affiche l\'aperçu', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Initially no logo
		await expect(page.getByText(/aucun logo configuré/i)).toBeVisible();

		// Trigger file chooser and upload the test PNG
		const fileChooserPromise = page.waitForEvent('filechooser');
		await page.getByText('Importer un logo').click();
		const fileChooser = await fileChooserPromise;

		const responsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/school/branding/logo') && resp.request().method() === 'POST'
		);
		await fileChooser.setFiles({
			name: 'logo.png',
			mimeType: 'image/png',
			buffer: TEST_LOGO_PNG
		});
		await responsePromise;

		// Success message
		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
		await expect(page.locator('.alert-success')).toContainText(/logo mis à jour/i);

		// Logo image is now visible
		await expect(page.locator('.logo-image')).toBeVisible();

		// "Changer le logo" and "Supprimer" buttons visible
		await expect(page.getByText('Changer le logo')).toBeVisible();
		await expect(
			page.getByRole('button', { name: /supprimer/i })
		).toBeVisible();

		// Placeholder text is gone
		await expect(page.getByText(/aucun logo configuré/i)).not.toBeVisible();
	});

	// ============================================================================
	// [P2] Delete logo returns to no-logo state (AC2)
	// ============================================================================
	test('[P2] supprimer logo revient à l\'état sans logo', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Logo should be visible from previous test
		await expect(page.locator('.logo-image')).toBeVisible();

		// Accept the confirmation dialog, wait for DELETE response, then click
		page.once('dialog', (dialog) => dialog.accept());
		const responsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/school/branding/logo') && resp.request().method() === 'DELETE'
		);
		await page.getByRole('button', { name: /supprimer/i }).click();
		await responsePromise;

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

		// Back to placeholder state
		await expect(page.getByText(/aucun logo configuré/i)).toBeVisible();
		await expect(page.getByText('Importer un logo')).toBeVisible();
	});

	// ============================================================================
	// [P2] Reset restores default theme (AC4)
	// ============================================================================
	test('[P2] réinitialiser restaure le thème par défaut', async ({ page }) => {
		await loginAsAdmin(page);
		await page.goto(`${ALPHA_URL}/admin/branding`);
		await waitForPageLoaded(page);

		// Color should be set from test 3
		await expect(page.locator('#primaryColor')).toHaveValue('#1E3A5F');

		// Accept the confirmation dialog, wait for PUT response, then click
		page.once('dialog', (dialog) => dialog.accept());
		const responsePromise = page.waitForResponse(
			(resp) => resp.url().includes('/school/branding') && resp.request().method() === 'PUT'
		);
		await page.getByRole('button', { name: /réinitialiser/i }).click();
		await responsePromise;

		// Success message
		await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
		await expect(page.locator('.alert-success')).toContainText(/couleurs mises à jour/i);

		// Color input is now empty
		await expect(page.locator('#primaryColor')).toHaveValue('');

		// CSS variables removed
		const accentPrimary = await page.evaluate(() =>
			document.documentElement.style.getPropertyValue('--accent-primary')
		);
		expect(accentPrimary).toBe('');

		// Preview swatch should not be visible (no primary color set)
		await expect(page.locator('.preview-swatch')).not.toBeVisible();
	});
});
