import { expect, type Browser, type Page, request } from '@playwright/test';
import { test } from '../fixtures/ids';
import { CleanupRegistry } from '../fixtures/cleanup';
import { adminApi } from '../fixtures/api';

// Phase B6 — Instance Users (/admin/users). Covers the bootstrap
// admin's view of the user directory: invite, edit, deactivate,
// search, delete, plus the member-role redirect and adversarial
// inputs to the invite form.

const API_BASE = process.env.E2E_API_BASE ?? 'http://127.0.0.1:18080';

const cleanup = new CleanupRegistry();
test.afterEach(async () => {
	await cleanup.run();
});

async function createMember(username: string, password = 'e2e-member-pw'): Promise<string> {
	const api = await adminApi();
	try {
		const res = await api.post('/api/v1/admin/admins', {
			data: { username, password, instance_role: 'member' }
		});
		expect(res.ok()).toBe(true);
		return ((await res.json()) as { id: string }).id;
	} finally {
		await api.dispose();
	}
}

async function loginToken(username: string, password: string): Promise<string> {
	const ctx = await request.newContext({ baseURL: API_BASE });
	try {
		const res = await ctx.post('/api/v1/admin/auth/login', {
			data: { username, password },
			headers: { 'content-type': 'application/json' }
		});
		expect(res.ok()).toBe(true);
		return ((await res.json()) as { token: string }).token;
	} finally {
		await ctx.dispose();
	}
}

async function pageWithToken(browser: Browser, token: string): Promise<Page> {
	const ctx = await browser.newContext({ storageState: undefined });
	const page = await ctx.newPage();
	await page.goto('/admin/login');
	await page.evaluate(
		([key, value]) => {
			localStorage.setItem(key, value);
		},
		['picloud.admin.token', token]
	);
	return page;
}

test.describe('B6 instance users', () => {
	test('invite happy path: form → reveal modal → user in list', async ({
		page,
		uniqueUsername
	}) => {
		const username = uniqueUsername('inv');

		await page.goto('/admin/users');
		await page.getByRole('button', { name: '+ Invite user' }).click();
		const modal = page.locator('form.modal');
		await modal.getByLabel('Username').fill(username);
		await modal.getByRole('radio', { name: /^Member/ }).check();
		await modal.getByRole('button', { name: /^Create user$/ }).click();

		// Reveal modal shows the one-time password.
		const reveal = page.locator('.reveal-modal');
		await expect(reveal).toBeVisible();
		await expect(reveal).toContainText(/User created — /);
		await expect(reveal.getByRole('button', { name: /^Done$/ })).toBeDisabled();
		await reveal.getByRole('checkbox', { name: /shared this/i }).check();
		await reveal.getByRole('button', { name: /^Done$/ }).click();

		// Now in the table.
		await expect(page.locator('.row:not(.head-row):not(.empty-row)', { hasText: username })).toBeVisible();

		// API cleanup — we don't have the user id from the UI alone.
		const api = await adminApi();
		try {
			const list = await api.get('/api/v1/admin/admins');
			const all = (await list.json()) as Array<{ id: string; username: string }>;
			const u = all.find((x) => x.username === username);
			if (u) cleanup.adminUser(u.id);
		} finally {
			await api.dispose();
		}
	});

	test('username live validation: bad chars → submit disabled', async ({ page }) => {
		await page.goto('/admin/users');
		await page.getByRole('button', { name: '+ Invite user' }).click();
		const modal = page.locator('form.modal');
		await modal.getByLabel('Username').fill('UPPER_CASE_invalid');
		await expect(modal.locator('small.invalid')).toContainText(/allowed pattern/i);
		await modal.getByRole('radio', { name: /^Member/ }).check();
		await expect(modal.getByRole('button', { name: /^Create user$/ })).toBeDisabled();
	});

	test('search filters the table by username', async ({ page, uniqueUsername }) => {
		const target = uniqueUsername('hit');
		const decoy = uniqueUsername('miss');
		const ids = await Promise.all([createMember(target), createMember(decoy)]);
		ids.forEach((id) => cleanup.adminUser(id));

		await page.goto('/admin/users');
		await page.getByPlaceholder(/Search by username/).fill(target);
		await expect(page.locator('.row', { hasText: target })).toBeVisible();
		await expect(page.locator('.row', { hasText: decoy })).toHaveCount(0);
	});

	test('deactivate confirm modal: Cancel keeps active, Deactivate flips, reactivate is one click', async ({
		page,
		uniqueUsername
	}) => {
		const username = uniqueUsername('toggle');
		const userId = await createMember(username);
		cleanup.adminUser(userId);

		await page.goto('/admin/users');
		await page.getByPlaceholder(/Search by username/).fill(username);
		const row = page.locator('.row:not(.head-row):not(.empty-row)', { hasText: username });
		await expect(row).toBeVisible();

		// Deactivate opens the confirm modal.
		await row.getByRole('button', { name: new RegExp(`User actions for ${username}`) }).click();
		await page.getByRole('menuitem', { name: /^Deactivate$/ }).click();
		const dialog = page.getByRole('dialog');
		await expect(dialog).toBeVisible();
		await expect(dialog).toContainText(username);

		// Cancel leaves the user active.
		await dialog.getByRole('button', { name: /^Cancel$/ }).click();
		await expect(dialog).toHaveCount(0);
		await expect(row).not.toContainText(/inactive/i);

		// Open again and confirm — user becomes inactive.
		await row.getByRole('button', { name: new RegExp(`User actions for ${username}`) }).click();
		await page.getByRole('menuitem', { name: /^Deactivate$/ }).click();
		await page.getByRole('dialog').getByRole('button', { name: /^Deactivate$/ }).click();
		await expect(row).toContainText(/inactive/i);

		// Reactivate is still one-click (non-destructive — no modal).
		await row.getByRole('button', { name: new RegExp(`User actions for ${username}`) }).click();
		await page.getByRole('menuitem', { name: /^Reactivate$/ }).click();
		await expect(row).not.toContainText(/inactive/i);
	});

	test('delete: wrong phrase keeps disabled, right phrase removes the user', async ({
		page,
		uniqueUsername
	}) => {
		const username = uniqueUsername('del');
		const userId = await createMember(username);
		cleanup.adminUser(userId);

		await page.goto('/admin/users');
		await page.getByPlaceholder(/Search by username/).fill(username);
		const row = page.locator('.row:not(.head-row):not(.empty-row)', { hasText: username });
		await row.getByRole('button', { name: new RegExp(`User actions for ${username}`) }).click();
		await page.getByRole('menuitem', { name: /^Delete$/ }).click();

		const dialog = page.getByRole('dialog');
		const confirm = dialog.getByRole('button', { name: /^Delete user$/ });
		await expect(confirm).toBeDisabled();
		await dialog.getByRole('textbox').fill('not-the-username');
		await expect(confirm).toBeDisabled();
		await dialog.getByRole('textbox').fill(username);
		await expect(confirm).toBeEnabled();
		await confirm.click();

		await expect(page.locator('.row:not(.head-row):not(.empty-row)', { hasText: username })).toHaveCount(0);
	});

	test('member-role user visiting /admin/users is bounced to profile with denied banner', async ({
		browser,
		uniqueUsername
	}) => {
		const username = uniqueUsername('memvw');
		const password = 'e2e-member-pw';
		const userId = await createMember(username, password);
		cleanup.adminUser(userId);

		const token = await loginToken(username, password);
		const memberPage = await pageWithToken(browser, token);
		try {
			await memberPage.goto('/admin/users');
			await expect(memberPage).toHaveURL(/\/admin\/profile\?denied=users$/);
			await expect(memberPage.getByText(/don.?t have access to the Users page/i)).toBeVisible();
		} finally {
			await memberPage.context().close();
		}
	});
});

test.describe('B6 instance users adversarial', () => {
	test('username too short: live invalid + submit disabled', async ({ page }) => {
		await page.goto('/admin/users');
		await page.getByRole('button', { name: '+ Invite user' }).click();
		const modal = page.locator('form.modal');
		await modal.getByLabel('Username').fill('a'); // 1 char — minimum is 2
		await expect(modal.locator('small.invalid')).toBeVisible();
		await modal.getByRole('radio', { name: /^Member/ }).check();
		await expect(modal.getByRole('button', { name: /^Create user$/ })).toBeDisabled();
	});

	test('email with script tag fails validation, never executes', async ({ page }) => {
		page.on('dialog', async (d) => {
			await d.dismiss();
			throw new Error(`Unexpected dialog: ${d.message()}`);
		});

		await page.goto('/admin/users');
		await page.getByRole('button', { name: '+ Invite user' }).click();
		const modal = page.locator('form.modal');
		await modal.getByLabel(/Email/).fill('<script>alert(1)</script>@x');
		await expect(modal.locator('small.invalid')).toContainText(/email/i);
	});
});
