// Helpers used by every test. The shape favours the JSON API for
// reads + accept/dispatch (deterministic, easy to assert) and the
// browser for human-facing surfaces (login form, dashboard render).

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

export const baseURL = process.env.RM_BASE_URL ?? 'http://127.0.0.1:8080';

export interface HostJSON {
    id: string;
    name: string;
    status: string;
    repo_status?: string;
    last_backup_status?: string;
}

export async function readBootstrapToken(): Promise<string> {
    const tok = process.env.RM_BOOTSTRAP_TOKEN;
    if (!tok) {
        throw new Error('RM_BOOTSTRAP_TOKEN not set — the harness scrapes it from server logs');
    }
    return tok;
}

export async function bootstrapAdmin(
    request: APIRequestContext,
    {
        username = 'admin',
        password = 'e2e-test-password-1234',
    }: { username?: string; password?: string } = {},
): Promise<{ username: string; password: string }> {
    const token = await readBootstrapToken();
    const res = await request.post(`${baseURL}/api/bootstrap`, {
        data: { token, username, password },
    });
    if (!res.ok() && res.status() !== 409 /* already bootstrapped */) {
        throw new Error(`bootstrap: ${res.status()} ${await res.text()}`);
    }
    return { username, password };
}

export async function loginViaUI(page: Page, username: string, password: string): Promise<void> {
    await page.goto(`${baseURL}/login`);
    await page.locator('#login-username').fill(username);
    await page.locator('#login-password').fill(password);
    await Promise.all([
        page.waitForURL(new RegExp(`^${baseURL}/?$`)),
        page.locator('form[action="/login"] button[type="submit"]').click(),
    ]);
}

/**
 * Polls the dashboard until a pending host card is visible, then
 * extracts its pending-id from the inline accept form's action URL.
 */
export async function waitForPendingHostID(page: Page): Promise<string> {
    const formLocator = page.locator('form[action^="/api/pending-hosts/"][action$="/accept"]').first();
    await expect(formLocator).toBeVisible({ timeout: 60_000 });
    const action = await formLocator.getAttribute('action');
    if (!action) throw new Error('pending host form has no action attribute');
    const m = action.match(/\/api\/pending-hosts\/([^/]+)\/accept/);
    if (!m) throw new Error(`unexpected action URL: ${action}`);
    return m[1];
}

export async function acceptPending(
    request: APIRequestContext,
    cookie: string,
    pendingID: string,
    repo: { url: string; username?: string; password: string },
): Promise<void> {
    const res = await request.post(`${baseURL}/api/pending-hosts/${pendingID}/accept`, {
        headers: { cookie, 'content-type': 'application/json' },
        data: {
            repo_url: repo.url,
            repo_username: repo.username ?? '',
            repo_password: repo.password,
        },
    });
    if (!res.ok()) {
        throw new Error(`accept: ${res.status()} ${await res.text()}`);
    }
}

export async function listHosts(request: APIRequestContext, cookie: string): Promise<HostJSON[]> {
    const res = await request.get(`${baseURL}/api/hosts`, { headers: { cookie } });
    if (!res.ok()) throw new Error(`list hosts: ${res.status()} ${await res.text()}`);
    const body = (await res.json()) as { items?: HostJSON[]; hosts?: HostJSON[] };
    return body.items ?? body.hosts ?? [];
}

export async function waitForHostStatus(
    request: APIRequestContext,
    cookie: string,
    matcher: (h: HostJSON) => boolean,
    timeoutMs = 60_000,
): Promise<HostJSON> {
    const deadline = Date.now() + timeoutMs;
    let last: HostJSON | undefined;
    while (Date.now() < deadline) {
        const hosts = await listHosts(request, cookie);
        const hit = hosts.find(matcher);
        if (hit) return hit;
        last = hosts[0];
        await new Promise((r) => setTimeout(r, 1_000));
    }
    throw new Error(`waitForHostStatus: timeout. Last seen: ${JSON.stringify(last)}`);
}

export async function createSourceGroup(
    request: APIRequestContext,
    cookie: string,
    hostID: string,
    body: { name: string; includes: string[]; excludes?: string[] },
): Promise<string> {
    const res = await request.post(`${baseURL}/api/hosts/${hostID}/source-groups`, {
        headers: { cookie, 'content-type': 'application/json' },
        data: {
            name: body.name,
            includes: body.includes,
            excludes: body.excludes ?? [],
            retention_policy: {},
            retry_max: 0,
            retry_backoff_seconds: 0,
        },
    });
    if (!res.ok()) throw new Error(`createSourceGroup: ${res.status()} ${await res.text()}`);
    const created = (await res.json()) as { id?: string; group?: { id?: string } };
    const id = created.id ?? created.group?.id;
    if (!id) throw new Error(`createSourceGroup: no id in response: ${JSON.stringify(created)}`);
    return id;
}

export async function runSourceGroup(
    request: APIRequestContext,
    cookie: string,
    hostID: string,
    groupID: string,
): Promise<void> {
    const res = await request.post(
        `${baseURL}/api/hosts/${hostID}/source-groups/${groupID}/run`,
        { headers: { cookie } },
    );
    if (!res.ok()) throw new Error(`runSourceGroup: ${res.status()} ${await res.text()}`);
}

export async function getSessionCookie(page: Page): Promise<string> {
    const cookies = await page.context().cookies();
    const c = cookies.find((c) => c.name === 'rm_session');
    if (!c) throw new Error('rm_session cookie not set after login');
    return `${c.name}=${c.value}`;
}
