// ==========================================
// Switchboard Core — SDK: Auth Module
// ==========================================
// Owns token storage, login/logout/refresh lifecycle,
// and the permissions/teams/policies cache.
//
// Factory: createAuth()
// Wire after creation: _setRestClient(client), _setEmit(fn)
// ==========================================

const BASE = window.__BASE__ || '';
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';

/**
 * Create the auth module.
 * Must call _setRestClient() and _setEmit() before use.
 *
 * @returns {object} auth
 */
export function createAuth() {

    // ── Private state ───────────────────────────

    let _accessToken = null;
    let _refreshToken = null;
    let _user = null;
    let _permissions = new Set();
    let _teams = [];
    let _groups = [];
    let _policies = {};
    let _refreshTimer = null;
    let _refreshPromise = null;
    let _booting = false;
    let _restClient = null;
    let _emit = () => {};

    // ── Token persistence ───────────────────────

    function _saveTokens() {
        localStorage.setItem(_storageKey, JSON.stringify({
            accessToken: _accessToken,
            refreshToken: _refreshToken,
            user: _user,
        }));
        // Cookie sync for Go template page auth
        if (_accessToken) {
            document.cookie = `sb_token=${_accessToken}; path=/; max-age=900; SameSite=Strict`;
        } else {
            document.cookie = 'sb_token=; path=/; max-age=0';
        }
    }

    function _loadTokens() {
        try {
            const saved = JSON.parse(localStorage.getItem(_storageKey) || 'null');
            if (saved) {
                _accessToken = saved.accessToken || null;
                _refreshToken = saved.refreshToken || null;
                _user = saved.user || null;
            }
        } catch (_) { /* corrupt storage */ }
    }

    function _clearTokens() {
        _accessToken = null;
        _refreshToken = null;
        _user = null;
        _permissions = new Set();
        _teams = [];
        _groups = [];
        _policies = {};
        localStorage.removeItem(_storageKey);
        sessionStorage.removeItem('sw-chat-active');
        document.cookie = 'sb_token=; path=/; max-age=0';
        if (_refreshTimer) { clearTimeout(_refreshTimer); _refreshTimer = null; }
    }

    // ── Token lifecycle ─────────────────────────

    function _setAuth(data) {
        _accessToken = data.access_token;
        _refreshToken = data.refresh_token;
        _user = data.user;
        _saveTokens();
        _scheduleRefresh(data.expires_in || 900);
    }

    function _scheduleRefresh(expiresIn) {
        if (_refreshTimer) clearTimeout(_refreshTimer);
        // Refresh at 80% of expiry (e.g., 12min for 15min token)
        const ms = Math.max((expiresIn * 0.8) * 1000, 30000);
        _refreshTimer = setTimeout(async () => {
            if (!_refreshToken) return;
            console.debug('[sw.auth] Proactive token refresh');
            try {
                const data = await _restClient.post('/api/v1/auth/refresh', {
                    refresh_token: _refreshToken,
                });
                _setAuth(data);
                _emit('auth.refresh', {}, { localOnly: true });
            } catch (e) {
                console.warn('[sw.auth] Proactive refresh failed, token still valid');
            }
        }, ms);
    }

    // ── Permissions fetch ───────────────────────

    async function _fetchPermissions() {
        try {
            const data = await _restClient.get('/api/v1/profile/permissions');
            _permissions = new Set(data.permissions || []);
            _teams = Object.freeze(data.teams || []);
            _groups = Object.freeze(data.groups || []);
            _policies = Object.freeze(data.policies || {});
            _emit('auth.permissions.changed', {}, { localOnly: true });
        } catch (e) {
            console.warn('[sw.auth] Failed to fetch permissions:', e.message);
            throw e;
        }
    }

    // ── Public API ──────────────────────────────

    const auth = {
        // ── Getters ─────────────────────────────

        get isAuthenticated() { return !!_accessToken; },

        get user() {
            if (!_user) return null;
            return Object.freeze({
                id:           _user.id,
                username:     _user.username,
                display_name: _user.display_name || _user.username,
                email:        _user.email || null,
                role:         _user.role || 'user',
                avatar:       _user.avatar_url || _user.avatar || null,
            });
        },

        get permissions() { return _permissions; },
        get teams()       { return _teams; },
        get groups()      { return _groups; },
        get policies()    { return _policies; },

        // ── Lifecycle ───────────────────────────

        /**
         * Login with credentials. Stores tokens, fetches permissions, emits event.
         */
        async login(login, password) {
            const data = await _restClient.post('/api/v1/auth/login', { login, password });
            _setAuth(data);
            await _fetchPermissions();
            _emit('auth.login', { user: auth.user }, { localOnly: true });
            return auth.user;
        },

        /**
         * Logout. Clears tokens, emits event. Server call is best-effort.
         */
        async logout() {
            try {
                await _restClient.post('/api/v1/auth/logout', { refresh_token: _refreshToken });
            } catch (_) { /* best effort */ }
            _clearTokens();
            _emit('auth.logout', {}, { localOnly: true });
        },

        /**
         * Manual token refresh. Returns true on success, false on failure.
         */
        async refresh() {
            if (_refreshPromise) return _refreshPromise;
            _refreshPromise = (async () => {
                try {
                    const data = await _restClient.post('/api/v1/auth/refresh', {
                        refresh_token: _refreshToken,
                    });
                    _setAuth(data);
                    return true;
                } catch (_) {
                    _clearTokens();
                    return false;
                } finally {
                    _refreshPromise = null;
                }
            })();
            return _refreshPromise;
        },

        /**
         * Boot sequence. Called by index.js on startup.
         * Loads tokens from localStorage, validates, fetches permissions.
         */
        async boot() {
            _loadTokens();
            if (!_refreshToken) {
                // No tokens in localStorage — clear any stale sb_token cookie
                // so Go SSR middleware doesn't trust a leftover cookie.
                document.cookie = 'sb_token=; path=/; max-age=0';
                return;
            }

            // Unknown token age — schedule refresh soon
            _scheduleRefresh(60);

            // Suppress _on401Failure redirect during boot — boot handles 401 itself.
            _booting = true;

            // Fetch permissions (validates the access token)
            try {
                await _fetchPermissions();
            } catch (e) {
                if (e.status === 401) {
                    // Token expired — try refresh
                    const ok = await auth.refresh();
                    if (ok) {
                        try { await _fetchPermissions(); }
                        catch (_) { _clearTokens(); }
                    }
                    // If refresh failed, _clearTokens already called
                } else {
                    // Network error or other — keep tokens, user may be offline
                    console.warn('[sw.auth] Boot: permissions fetch failed (keeping tokens):', e.message);
                }
            } finally {
                _booting = false;
            }

            if (_accessToken) {
                _emit('auth.boot', { user: auth.user }, { localOnly: true });
            }
        },

        // ── Internal (called by rest-client / index.js) ──

        _getToken() { return _accessToken; },

        async _onRefresh() {
            if (_refreshToken) return auth.refresh();
            return false;
        },

        _on401Failure() {
            _clearTokens();
            // During boot(), auth.boot() handles 401 gracefully — don't redirect.
            if (_booting) return;
            // Already on login page — don't redirect (prevents blank-page reload loop
            // when stale localStorage tokens trigger 401 during SDK boot on /login).
            const base = window.__BASE__ || '';
            const loginPath = base + '/login';
            const here = window.location.pathname.replace(/\/+$/, '');
            if (here === loginPath) return;
            window.location.href = loginPath;
        },

        _setAuth(data) { _setAuth(data); },
        _setRestClient(client) { _restClient = client; },
        _setEmit(fn) { _emit = fn; },
    };

    return auth;
}
