import { describe, it, expect, vi, beforeEach } from 'vitest';

// Mock the API client *before* importing the load function so the
// module under test picks up the mock when it resolves its imports.
vi.mock('$lib/api/auth', () => ({
    getAuthConfig: vi.fn(),
    me: vi.fn()
}));

import { load } from './+layout';
import { getAuthConfig, me, type AuthConfig } from '$lib/api/auth';

type MinimalLoadEvent = { url: { pathname: string; search: string } };

function event(pathname: string, search = ''): MinimalLoadEvent {
    return { url: { pathname, search } };
}

// `LayoutLoad`'s declared return type is `void | …`. Our `load`
// always returns `{ authConfig }`, but TypeScript can't narrow on
// that at the call site. Wrap to remove the `void` arm so the
// assertions stay terse.
async function callLoad(ev: MinimalLoadEvent): Promise<{ authConfig: AuthConfig }> {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const result = await load(ev as any);
    return result as { authConfig: AuthConfig };
}

const PUBLIC_CFG = { self_register_enabled: true, private_mode: false };
const PRIVATE_CFG = { self_register_enabled: false, private_mode: true };

const aliceUser = {
    id: 'u1',
    username: 'alice',
    created_at: '2026-01-01T00:00:00Z',
    is_admin: false
};

describe('root +layout load', () => {
    beforeEach(() => {
        vi.mocked(getAuthConfig).mockReset();
        vi.mocked(me).mockReset();
    });

    it('public mode: returns authConfig data, never calls me()', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PUBLIC_CFG);
        const data = await callLoad(event('/'));
        expect(data.authConfig).toEqual(PUBLIC_CFG);
        expect(me).not.toHaveBeenCalled();
    });

    it('private mode + anonymous on `/`: throws redirect(302) to /login with next=', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PRIVATE_CFG);
        vi.mocked(me).mockResolvedValue(null);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        await expect(load(event('/') as any)).rejects.toMatchObject({
            status: 302,
            location: '/login?next=%2F'
        });
    });

    it('private mode + anonymous on `/login`: passes through without redirect', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PRIVATE_CFG);
        const data = await callLoad(event('/login'));
        expect(data.authConfig.private_mode).toBe(true);
        // me() must not run on the login page itself, otherwise anonymous
        // visits make an extra round-trip every page load.
        expect(me).not.toHaveBeenCalled();
    });

    it('private mode + logged-in user: no redirect, returns authConfig', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PRIVATE_CFG);
        vi.mocked(me).mockResolvedValue(aliceUser);
        const data = await callLoad(event('/'));
        expect(data.authConfig).toEqual(PRIVATE_CFG);
    });

    it('private mode + anonymous: preserves pathname AND search in next=', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PRIVATE_CFG);
        vi.mocked(me).mockResolvedValue(null);
        await expect(
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            load(event('/manga/abc', '?page=3') as any)
        ).rejects.toMatchObject({
            status: 302,
            location: '/login?next=%2Fmanga%2Fabc%3Fpage%3D3'
        });
    });

    it('private mode + anonymous on /register: redirects to /login (register is never reachable in private mode)', async () => {
        vi.mocked(getAuthConfig).mockResolvedValue(PRIVATE_CFG);
        vi.mocked(me).mockResolvedValue(null);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        await expect(load(event('/register') as any)).rejects.toMatchObject({
            status: 302,
            location: '/login?next=%2Fregister'
        });
    });

    it('getAuthConfig failure: falls back to public-mode defaults, no redirect', async () => {
        // The backend middleware is the source of truth for the gate;
        // if the config probe blips, fail soft so a brief outage doesn't
        // lock everyone out of even the login page. No private data
        // can leak because the backend still 401s every request.
        vi.mocked(getAuthConfig).mockRejectedValue(new Error('network'));
        const data = await callLoad(event('/'));
        expect(data.authConfig).toEqual({
            self_register_enabled: true,
            private_mode: false
        });
        expect(me).not.toHaveBeenCalled();
    });
});
