/**
 * Hotfix smoke — login form must not be able to leak credentials to
 * the URL via a native HTML form submit.
 *
 * Background:
 *   Pre-fix, /login rendered <form onSubmit={...}> with no method=,
 *   plus <input name="username"> and <input name="password" type="password">.
 *   If React hydration failed (e.g. an exception in a decorative
 *   client component aborted hydration of the route), the browser's
 *   native form submission kicked in: a GET to the current URL with
 *   both fields serialised as query params. Result: a plaintext
 *   password in the address bar, browser history, access log, and
 *   Referer header.
 *
 * Defence (layered):
 *   1. Inputs are controlled and have NO `name=` attribute, so even
 *      a native submit serialises nothing.
 *   2. The submit button is `type="button"` (not "submit"), so it is
 *      not a form submitter.
 *   3. LoginBackground is imported via next/dynamic with ssr:false,
 *      so a crash inside it cannot abort hydration of the form.
 *   4. middleware.ts strips username/password from /login query +
 *      sets Referrer-Policy: no-referrer on /login.
 *
 * This smoke asserts each of these statically so the regression can't
 * silently come back via a refactor.
 */
import './_lib';
import { ok, fail } from './_lib';
import { readFileSync } from 'fs';
import path from 'path';

const ROOT = path.resolve(__dirname, '../..');
// audit3 (2026-05-28): the actual login form lives in page-content.tsx
// after the server-wrapper split for SEO metadata. page.tsx is now a
// thin server component that renders <PageContent />.
const LOGIN_PAGE = path.join(ROOT, 'src/app/login/page-content.tsx');
const MIDDLEWARE = path.join(ROOT, 'src/proxy.ts');

function read(rel: string, abs: string): string {
    try {
        return readFileSync(abs, 'utf8');
    } catch (err) {
        fail(`read ${rel}`, String(err));
    }
}

function assertNotPresent(label: string, src: string, needle: string | RegExp): void {
    const re = typeof needle === 'string' ? new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) : needle;
    if (re.test(src)) {
        fail(label, `pattern still present: ${String(needle)}`);
    }
    ok(label);
}

function assertPresent(label: string, src: string, needle: string | RegExp): void {
    const re = typeof needle === 'string' ? new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) : needle;
    if (!re.test(src)) {
        fail(label, `missing required pattern: ${String(needle)}`);
    }
    ok(label);
}

function main(): void {
    const login = read('src/app/login/page-content.tsx', LOGIN_PAGE);
    const middleware = read('src/proxy.ts', MIDDLEWARE);

    // Layer 1: no name="username"/name="password" on the credential inputs.
    // These attributes are the only way native form submission can carry
    // field values into the URL or POST body. Negative-lookbehind on
    // backtick + word-char so the regex matches JSX attributes but not
    // text inside a code-quoted comment (``name="..."``).
    assertNotPresent('login: no name="username"', login, /(?<![`\w])name=["']username["']/);
    assertNotPresent('login: no name="password"', login, /(?<![`\w])name=["']password["']/);

    // Layer 1: inputs are controlled. defaultValue=admin/admin was the
    // pre-fix shape; controlled inputs use value=/onChange= instead.
    assertNotPresent('login: no defaultValue on inputs', login, /defaultValue=/);
    assertPresent('login: controlled username input', login, /value=\{username\}/);
    assertPresent('login: controlled password input', login, /value=\{password\}/);

    // Layer 2: the Sign In button must NOT be a form submitter. It must
    // be type="button" wired to onClick. (Strict regex: a `type="submit"`
    // anywhere in the file would fail the smoke.)
    assertNotPresent('login: no submit-button anywhere', login, /type=["']submit["']/);
    assertPresent('login: Sign In button uses onClick', login, /onClick=\{handleCredentialsLogin\}/);

    // Layer 2: form has method="post" as belt-and-suspenders so a
    // native fallback would POST (not GET) to current URL.
    assertPresent('login: form method="post"', login, /<form[^>]*method=["']post["']/);

    // Layer 3: LoginBackground must be imported via next/dynamic with
    // ssr:false so a crash inside it can't abort hydration of the form.
    assertNotPresent(
        "login: no direct ESM import of LoginBackground",
        login,
        /^\s*import LoginBackground from/m,
    );
    assertPresent(
        "login: LoginBackground via next/dynamic",
        login,
        /dynamic\(\s*\(\)\s*=>\s*import\(['"]@\/components\/auth\/LoginBackground['"]\)/,
    );
    assertPresent(
        "login: dynamic has ssr: false",
        login,
        /ssr:\s*false/,
    );

    // Layer 4: middleware must strip ?username / ?password on GET /login
    // BEFORE rendering, and must set Referrer-Policy: no-referrer on /login
    // responses so the URL can't leak via Referer to third-party assets.
    assertPresent(
        'middleware: strips username from /login query',
        middleware,
        /searchParams.*username|sp\.has\(['"]username['"]\)/,
    );
    assertPresent(
        'middleware: strips password from /login query',
        middleware,
        /searchParams.*password|sp\.has\(['"]password['"]\)/,
    );
    assertPresent(
        'middleware: clears creds on redirect',
        middleware,
        /searchParams\.delete\(['"]password['"]\)/,
    );
    assertPresent(
        'middleware: Referrer-Policy: no-referrer on /login',
        middleware,
        /Referrer-Policy['"]\s*,\s*['"]no-referrer['"]/,
    );

    ok('hotfix-login-credential-leak', 'all 5 defence layers in place');
}

main();
