/**
 * Tests for security/auth-validation.js
 *
 * window.validatePasswordViaAPI must NEVER block submit on transport failures —
 * server-side validation is the source of truth, this is just a UX hint. So
 * every failure mode (non-OK response, network error) should resolve to
 * { valid: true, errors: [] }.
 */

import '@js/security/auth-validation.js';

describe('validatePasswordViaAPI', () => {
    let originalFetch;

    beforeEach(() => {
        originalFetch = globalThis.fetch;
    });

    afterEach(() => {
        globalThis.fetch = originalFetch;
    });

    it('returns the parsed JSON on a 200 response', async () => {
        const apiResult = { valid: false, errors: ['too short', 'no digits'] };
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: true,
            json: () => Promise.resolve(apiResult),
        });

        const r = await window.validatePasswordViaAPI('weak', 'csrf-abc');
        expect(r).toEqual(apiResult);
    });

    it('returns valid:true on 403 (CSRF expiry) so submit is not blocked', async () => {
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: false,
            status: 403,
            json: () => Promise.resolve({ error: 'CSRF expired' }),
        });

        const r = await window.validatePasswordViaAPI('whatever', 'stale');
        expect(r).toEqual({ valid: true, errors: [] });
    });

    it('returns valid:true on 500 (server error)', async () => {
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: false,
            status: 500,
            json: () => Promise.resolve({}),
        });

        const r = await window.validatePasswordViaAPI('x', 't');
        expect(r).toEqual({ valid: true, errors: [] });
    });

    it('returns valid:true when fetch rejects (network error)', async () => {
        globalThis.fetch = vi.fn().mockRejectedValue(new Error('Network down'));

        const r = await window.validatePasswordViaAPI('x', 't');
        expect(r).toEqual({ valid: true, errors: [] });
    });

    it('POSTs FormData with password and csrf_token fields', async () => {
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: true,
            json: () => Promise.resolve({ valid: true, errors: [] }),
        });

        await window.validatePasswordViaAPI('hunter2', 'token-xyz');

        expect(globalThis.fetch).toHaveBeenCalledTimes(1);
        const [url, opts] = globalThis.fetch.mock.calls[0];
        expect(url).toBe('/auth/validate-password');
        expect(opts.method).toBe('POST');
        expect(opts.body).toBeInstanceOf(FormData);
        expect(opts.body.get('password')).toBe('hunter2');
        expect(opts.body.get('csrf_token')).toBe('token-xyz');
    });

    it('does not set Content-Type header (FormData sets its own multipart boundary)', async () => {
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: true,
            json: () => Promise.resolve({ valid: true, errors: [] }),
        });

        await window.validatePasswordViaAPI('x', 't');

        const [, opts] = globalThis.fetch.mock.calls[0];
        // Either no headers at all, or no Content-Type override
        expect(opts.headers).toBeUndefined();
    });

    it('returns valid:true when JSON parsing throws on a 200 response', async () => {
        // If the server returns 200 but body is malformed, json() throws
        // and the catch returns the safe default
        globalThis.fetch = vi.fn().mockResolvedValue({
            ok: true,
            json: () => Promise.reject(new Error('bad JSON')),
        });

        const r = await window.validatePasswordViaAPI('x', 't');
        expect(r).toEqual({ valid: true, errors: [] });
    });
});
