import { expect, test } from '@playwright/test';

// Phase B8 — Cross-cutting security. Things that aren't tied to a
// single page: session handling, secret leakage, error states for
// missing resources, and a sanity check that no XSS sink fires
// anywhere in the dashboard's main authed routes.

const VALID_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin';
const VALID_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin';

test.describe('B8 cross-cutting security', () => {
	test('expired/stale token: any authed call redirects to /login', async ({ page }) => {
		// Replace the storageState token with an obvious garbage
		// value; the fetch wrapper treats 401 as "go to /login".
		await page.goto('/admin/login');
		await page.evaluate(() => {
			localStorage.setItem('picloud.admin.token', 'expired-or-bogus-token');
		});
		await page.goto('/admin/apps');
		await expect(page).toHaveURL(/\/admin\/login$/);
	});

	test('login response cookie is HttpOnly', async ({ request }) => {
		const res = await request.post('/api/v1/admin/auth/login', {
			data: { username: VALID_USERNAME, password: VALID_PASSWORD },
			headers: { 'content-type': 'application/json' }
		});
		expect(res.ok()).toBe(true);
		const headers = res.headers();
		const setCookie = headers['set-cookie'];
		// Backend may or may not set a cookie (the dashboard primarily
		// uses bearer-in-localStorage). If it does, it must be
		// HttpOnly so XSS can't exfiltrate it.
		if (setCookie) {
			expect(setCookie.toLowerCase()).toContain('httponly');
		}
	});

	test('bootstrap password is not present in the DOM after login', async ({ page }) => {
		await page.goto('/admin/apps');
		const body = await page.locator('body').innerText();
		expect(body).not.toContain(VALID_PASSWORD);
	});

	test('non-existent app slug shows a recoverable error, not a crash', async ({ page }) => {
		await page.goto('/admin/apps/does-not-exist-e2e-9999');
		// Page must render *something* and the layout must remain
		// intact (header link to Apps still works).
		await expect(page.getByRole('link', { name: 'Apps' })).toBeVisible();
		// And surface the failure to the user — either a "couldn't
		// load" message or a "back to apps" link.
		const errorOrBack = page.locator('.error, a[href$="/admin/apps"]');
		await expect(errorOrBack.first()).toBeVisible();
	});

	test('xss probe across major surfaces never fires a dialog', async ({ page }) => {
		page.on('dialog', async (dialog) => {
			await dialog.dismiss();
			throw new Error(
				`XSS sink fired — got a ${dialog.type()} dialog: "${dialog.message()}"`
			);
		});

		// Cover each main authed route. None should evaluate any
		// payload that earlier tests may have stored, and none should
		// inject inline <script> tags from server responses.
		for (const path of ['/admin/apps', '/admin/profile', '/admin/users']) {
			await page.goto(path);
			await page.waitForLoadState('domcontentloaded');
			const inlineScripts = await page.locator('script[src=""], script:not([src])').count();
			// Svelte itself injects no inline <script> in the
			// production bundle; vite dev does, but never with
			// onerror/alert payload text in them.
			const evilInline = await page
				.locator('script:has-text("alert"), script:has-text("__xss")')
				.count();
			expect(evilInline, `evil inline script tag on ${path}`).toBe(0);
			expect(inlineScripts).toBeGreaterThanOrEqual(0); // sanity assertion, no crash
		}
	});
});
