import assert from 'node:assert/strict';
import test from 'node:test';

let importSequence = 0;

class StorageMock {
    constructor(initial = {}) {
        this.values = new Map(Object.entries(initial));
        this.removed = [];
    }

    getItem(key) {
        return this.values.has(key) ? this.values.get(key) : null;
    }

    setItem(key, value) {
        this.values.set(key, String(value));
    }

    removeItem(key) {
        this.removed.push(key);
        this.values.delete(key);
    }
}

async function loadAuthModule() {
    importSequence += 1;
    return import(`../auth-session.js?test=${importSequence}`);
}

function installBrowserMocks() {
    globalThis.localStorage = new StorageMock({
        token: 'устаревший-access-token',
        role: 'ADMIN',
        departmentId: '7',
        userId: '11',
        theme: 'dark'
    });
    globalThis.sessionStorage = new StorageMock({ token: 'устаревший-session-token' });
}

function jsonResponse(body, status = 200) {
    return new Response(JSON.stringify(body), {
        status,
        headers: { 'Content-Type': 'application/json' }
    });
}

test('login сохраняет access JWT только в памяти и очищает legacy Web Storage', async () => {
    installBrowserMocks();
    const calls = [];
    globalThis.fetch = async (url, options) => {
        calls.push({ url, options });
        return jsonResponse({ ok: true });
    };

    const auth = await loadAuthModule();
    const state = auth.storeAuthState({
        token: 'access-login',
        role: 'ADMIN',
        departmentId: 7,
        userId: 11,
        redirect: '/admin/'
    });

    assert.equal(auth.getAccessToken(), 'access-login');
    assert.deepEqual(state, {
        role: 'ADMIN',
        departmentId: '7',
        userId: '11',
        redirect: '/admin/'
    });
    assert.equal(localStorage.getItem('token'), null);
    assert.equal(localStorage.getItem('role'), null);
    assert.equal(localStorage.getItem('theme'), 'dark');

    await auth.fetchWithAuth('/api/test');
    assert.equal(new Headers(calls[0].options.headers).get('Authorization'), 'Bearer access-login');
});

test('reload восстанавливает память через HttpOnly refresh-cookie', async () => {
    installBrowserMocks();
    const calls = [];
    globalThis.fetch = async (url, options) => {
        calls.push({ url, options });
        return jsonResponse({
            token: 'access-after-reload',
            role: 'DEPARTMENT',
            departmentId: 4,
            userId: 15,
            redirect: '/admin/#department-workspace'
        });
    };

    const auth = await loadAuthModule();
    const state = await auth.restoreSession();

    assert.equal(calls.length, 1);
    assert.equal(calls[0].url, '/api/auth/refresh');
    assert.equal(calls[0].options.method, 'POST');
    assert.equal(calls[0].options.credentials, 'same-origin');
    assert.equal(auth.getAccessToken(), 'access-after-reload');
    assert.equal(state.role, 'DEPARTMENT');
});

test('401 запускает одну ротацию refresh и повторяет запрос с новым access JWT', async () => {
    installBrowserMocks();
    const calls = [];
    globalThis.fetch = async (url, options) => {
        calls.push({ url, options });
        if (calls.length === 1) return jsonResponse({ message: 'Истёк токен' }, 401);
        if (url === '/api/auth/refresh') {
            return jsonResponse({ token: 'access-rotated', role: 'ADMIN' });
        }
        return jsonResponse({ success: true });
    };

    const auth = await loadAuthModule();
    auth.storeAuthState({ token: 'access-old', role: 'ADMIN' });
    const response = await auth.fetchWithAuth('/api/protected');

    assert.equal(response.status, 200);
    assert.deepEqual(calls.map(call => call.url), [
        '/api/protected',
        '/api/auth/refresh',
        '/api/protected'
    ]);
    assert.equal(new Headers(calls[0].options.headers).get('Authorization'), 'Bearer access-old');
    assert.equal(new Headers(calls[2].options.headers).get('Authorization'), 'Bearer access-rotated');
});

test('параллельные запросы используют одну refresh-ротацию', async () => {
    installBrowserMocks();
    let refreshCalls = 0;
    let releaseRefresh;
    const refreshGate = new Promise(resolve => {
        releaseRefresh = resolve;
    });
    globalThis.fetch = async url => {
        if (url === '/api/auth/refresh') {
            refreshCalls += 1;
            await refreshGate;
            return jsonResponse({ token: 'access-shared', role: 'ADMIN' });
        }
        return jsonResponse({ success: true });
    };

    const auth = await loadAuthModule();
    const first = auth.refreshAccessToken();
    const second = auth.refreshAccessToken();
    releaseRefresh();

    assert.equal(await first, true);
    assert.equal(await second, true);
    assert.equal(refreshCalls, 1);
});

test('logout отзывает cookie-сессию и очищает access JWT из памяти', async () => {
    installBrowserMocks();
    const calls = [];
    globalThis.fetch = async (url, options) => {
        calls.push({ url, options });
        return jsonResponse({ success: true });
    };

    const auth = await loadAuthModule();
    auth.storeAuthState({ token: 'access-before-logout', role: 'STUDENT', userId: 22 });
    await auth.logoutSession();

    assert.equal(auth.getAccessToken(), null);
    assert.equal(auth.getAuthState(), null);
    assert.equal(calls[0].url, '/api/auth/logout');
    assert.equal(calls[0].options.credentials, 'same-origin');
});
