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

// Regression coverage for the slug-vs-UUID admin-endpoint bug fixed by
// commit `aa493b9`: every per-app tab (`/admin/apps/[slug]/...`) must
// accept the URL slug, not just a UUID. The original report was the
// Queues tab; the underlying bug spanned 27 handlers across 6 backend
// files (queues_api, files_api, secrets_api, topics_api,
// dead_letters_api, triggers_api). This spec hits each tab in
// isolation against a freshly-created app — no dependence on the
// dev-seeded `default` app.
//
// What this catches:
//   - The original parse error ("Cannot parse app_id" / "UUID parsing
//     failed") surfaced as an error banner in the dashboard.
//   - Any non-2xx response to /api/v1/admin/apps/... during page load —
//     a broader net than the literal error text. A regression returning
//     500 with a different message would otherwise pass silently.
//
// What this does NOT catch:
//   - Authz-role-dependent behavior. Admin-only coverage; members /
//     viewers have their own specs.
//   - The queue drilldown's data path (we visit the drilldown but the
//     queue is empty, so the "no consumer" empty-state branch is what
//     renders).

const PARSE_ERROR = /Cannot parse|UUID parsing failed/i;

interface AppCtx {
	slug: string;
}

async function createApp(uniqueSlug: (p: string) => string, prefix: string): Promise<AppCtx> {
	const slug = uniqueSlug(prefix);
	const api = await adminApi();
	try {
		const res = await api.post('/api/v1/admin/apps', {
			data: { slug, name: slug }
		});
		if (!res.ok()) {
			throw new Error(`create app ${slug} failed: HTTP ${res.status()} ${await res.text()}`);
		}
	} finally {
		await api.dispose();
	}
	return { slug };
}

// Visit `path` and assert nothing broke. Captures every admin-API
// response and fails on any 4xx/5xx — a wider net than asserting on a
// specific error string. Also keeps the literal-parse-error check so
// the test reads as a regression for the original bug.
async function expectTabLoadsCleanly(page: Page, path: string): Promise<void> {
	const failures: string[] = [];
	const handler = (response: Response) => {
		const url = response.url();
		if (!url.includes('/api/v1/admin/apps/')) return;
		const status = response.status();
		if (status >= 400) {
			failures.push(`${status} ${url}`);
		}
	};
	page.on('response', handler);
	try {
		await page.goto(path);
		// Wait for a structural marker instead of `networkidle` — that
		// hook is officially discouraged for SPAs because background
		// polling (e.g., the queues page's 5s auto-refresh) keeps the
		// network "active" indefinitely. The <main> region is in
		// +layout.svelte so it's reliable.
		await expect(page.locator('main')).toBeVisible();
		await expect(
			page.getByText(PARSE_ERROR),
			`literal parse error on ${path}`
		).toHaveCount(0);
	} finally {
		page.off('response', handler);
	}
	if (failures.length > 0) {
		throw new Error(`HTTP failures on ${path}:\n  ${failures.join('\n  ')}`);
	}
}

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

// Each tab covers a different backend file:
//   - `''` (root)          → triggers_api, topics_api, secrets_api,
//                            files trigger metadata, dead-letter count
//   - `/queues`            → queues_api::list_queues
//   - `/queues/<name>`     → queues_api::get_queue (drilldown handler)
//   - `/files`             → files_api::list_files
//   - `/dead-letters`      → dead_letters_api::list + count
//   - `/users`             → users_admin_api::list_users (already
//                            lenient before the bug fix; included for
//                            completeness so the spec doesn't regress
//                            it later)
//   - `/users/invitations` → app_user_invitation_repo via the same
//                            admin surface
const TABS: Array<{ subpath: string; label: string }> = [
	{ subpath: '', label: 'main' },
	{ subpath: '/queues', label: 'queues' },
	{ subpath: '/queues/never-enqueued', label: 'queue drilldown' },
	{ subpath: '/workflows', label: 'workflows' },
	{ subpath: '/files', label: 'files' },
	{ subpath: '/dead-letters', label: 'dead-letters' },
	{ subpath: '/users', label: 'app users' },
	{ subpath: '/users/invitations', label: 'app invitations' }
];

test.describe('per-app tab navigation accepts slug URLs', () => {
	for (const tab of TABS) {
		test(`${tab.label} loads cleanly`, async ({ page, uniqueSlug }) => {
			// Register cleanup BEFORE the API call so a flaky create
			// still gets swept up. CleanupRegistry tolerates 404s, so a
			// no-op cleanup is harmless if creation failed entirely.
			const slug = uniqueSlug('nav');
			cleanup.app(slug);
			const api = await adminApi();
			try {
				const res = await api.post('/api/v1/admin/apps', {
					data: { slug, name: slug }
				});
				expect(res.ok()).toBe(true);
			} finally {
				await api.dispose();
			}
			await expectTabLoadsCleanly(page, `/admin/apps/${slug}${tab.subpath}`);
		});
	}

	test('queues tab specifically — the original bug report', async ({ page, uniqueSlug }) => {
		// Focused regression test for the original bug report. The slug
		// is unique (not "default") because dev seeding isn't guaranteed
		// in CI, but the failure mode being reproduced is identical.
		const slug = uniqueSlug('queues');
		cleanup.app(slug);
		const api = await adminApi();
		try {
			const res = await api.post('/api/v1/admin/apps', {
				data: { slug, name: slug }
			});
			expect(res.ok()).toBe(true);
		} finally {
			await api.dispose();
		}

		await expectTabLoadsCleanly(page, `/admin/apps/${slug}/queues`);
		// Positive assertion via stable testid. If a future copy edit
		// renames the empty-state text, this still anchors to the same
		// DOM element.
		await expect(page.getByTestId('queues-empty-state')).toBeVisible();
	});

	test('workflows tab renders its empty state (v1.2)', async ({ page, uniqueSlug }) => {
		const slug = uniqueSlug('wf');
		cleanup.app(slug);
		const api = await adminApi();
		try {
			const res = await api.post('/api/v1/admin/apps', {
				data: { slug, name: slug }
			});
			expect(res.ok()).toBe(true);
		} finally {
			await api.dispose();
		}
		await expectTabLoadsCleanly(page, `/admin/apps/${slug}/workflows`);
		// A fresh app has no workflows → the empty-state anchors the smoke test.
		await expect(page.getByTestId('workflows-empty-state')).toBeVisible();
	});
});
