import { chromium, request } from '@playwright/test';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const API_BASE = process.env.E2E_API_BASE ?? 'http://127.0.0.1:18080';
const DASHBOARD_PORT = Number(process.env.PICLOUD_DASHBOARD_PORT ?? 5173);
const DASHBOARD_ORIGIN = process.env.E2E_DASHBOARD_ORIGIN ?? `http://localhost:${DASHBOARD_PORT}`;
const ADMIN_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin';
const ADMIN_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin';

const AUTH_DIR = path.join(__dirname, '.auth');
const ADMIN_STATE_PATH = path.join(AUTH_DIR, 'admin.json');

export default async function globalSetup(): Promise<void> {
	await assertBackendUp();
	await fs.mkdir(AUTH_DIR, { recursive: true });
	const token = await loginAsAdmin();
	await sweepOrphans(token);
	await persistAdminStorageState(token);
}

async function assertBackendUp(): Promise<void> {
	const probe = await request.newContext();
	try {
		const res = await probe.get(`${API_BASE}/healthz`, { timeout: 5_000 });
		if (!res.ok()) {
			throw new Error(
				`backend /healthz returned ${res.status()} — is \`cargo run -p picloud\` listening on ${API_BASE}?`
			);
		}
	} catch (err) {
		throw new Error(
			`Could not reach backend at ${API_BASE}/healthz. ` +
				`Bring it up before running E2E tests:\n\n` +
				`  docker compose up -d postgres\n` +
				`  PICLOUD_BIND=127.0.0.1:18080 \\\n` +
				`  PICLOUD_ADMIN_USERNAME=${ADMIN_USERNAME} \\\n` +
				`  PICLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD} \\\n` +
				`  DATABASE_URL=postgres://picloud:picloud@127.0.0.1:15432/picloud \\\n` +
				`  cargo run -p picloud\n\n` +
				`Underlying error: ${(err as Error).message}`
		);
	} finally {
		await probe.dispose();
	}
}

async function loginAsAdmin(): Promise<string> {
	const ctx = await request.newContext();
	try {
		const res = await ctx.post(`${API_BASE}/api/v1/admin/auth/login`, {
			data: { username: ADMIN_USERNAME, password: ADMIN_PASSWORD },
			headers: { 'content-type': 'application/json' }
		});
		if (!res.ok()) {
			const body = await res.text();
			throw new Error(
				`Admin login failed (${res.status()}): ${body}. ` +
					`Verify PICLOUD_ADMIN_USERNAME / PICLOUD_ADMIN_PASSWORD match the seeded bootstrap admin.`
			);
		}
		const payload = (await res.json()) as { token?: string };
		if (!payload.token) {
			throw new Error('Admin login response missing token field');
		}
		return payload.token;
	} finally {
		await ctx.dispose();
	}
}

// Clean up apps + admin users left over from a previous crashed run.
// The convention is that every e2e-created resource has a slug
// starting with `e2e-` (apps) or a username starting with `e2e`
// (admins) — see fixtures/ids.ts. Best-effort: a sweep failure must
// not stop the suite from running.
async function sweepOrphans(token: string): Promise<void> {
	const ctx = await request.newContext({
		baseURL: API_BASE,
		extraHTTPHeaders: { authorization: `Bearer ${token}` }
	});
	try {
		try {
			const res = await ctx.get('/api/v1/admin/apps');
			if (res.ok()) {
				const apps = (await res.json()) as Array<{ slug: string }>;
				for (const app of apps) {
					if (!app.slug.startsWith('e2e-')) continue;
					try {
						await ctx.delete(
							`/api/v1/admin/apps/${encodeURIComponent(app.slug)}?force=true`
						);
					} catch {
						// Individual delete failure is non-fatal — the per-test
						// cleanup will catch it on the next run.
					}
				}
			}
		} catch {
			// Listing failed; nothing to do but proceed.
		}
		try {
			const res = await ctx.get('/api/v1/admin/admins');
			if (res.ok()) {
				const admins = (await res.json()) as Array<{ id: string; username: string }>;
				for (const a of admins) {
					if (!/^e2e/.test(a.username)) continue;
					try {
						await ctx.delete(`/api/v1/admin/admins/${a.id}`);
					} catch {
						// Same per-row tolerance as above.
					}
				}
			}
		} catch {
			// Listing failed; same as above.
		}
	} finally {
		await ctx.dispose();
	}
}

// The dashboard reads its session from localStorage under the key
// `picloud.admin.token` (see src/lib/auth.ts). We can't write to
// localStorage without a browser context, so launch a throwaway one,
// seed the value, then save storageState for every test to reuse.
async function persistAdminStorageState(token: string): Promise<void> {
	const browser = await chromium.launch();
	try {
		const context = await browser.newContext();
		const page = await context.newPage();
		await page.goto(`${DASHBOARD_ORIGIN}/admin/login`);
		await page.evaluate(
			([key, value]) => {
				localStorage.setItem(key, value);
			},
			['picloud.admin.token', token]
		);
		await context.storageState({ path: ADMIN_STATE_PATH });
	} finally {
		await browser.close();
	}
}
