/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/app/session.js
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
*/
import { ref } from 'vue';
import { supabase } from '@/lib/supabase/client';
import { logAuth, logError } from '@/support/supportLogger';

/**
 * ⚠️ IMPORTANTE — ESTABILIDADE DE NAVEGAÇÃO
 *
 * Este módulo controla a sessão global usada pelo router guard.
 *
 * Já houve uma condição de corrida entre:
 * - visibilitychange → refreshSession()
 * - supabase.auth.onAuthStateChange (SIGNED_IN)
 * - router.beforeEach
 *
 * Quando a aba voltava ao foco, refreshSession() era disparado,
 * o Supabase emitia SIGNED_IN redundante, e o guard aguardava
 * tenant + entitlements simultaneamente à re-hidratação da sessão.
 *
 * Isso fazia a navegação "travar" no meio do beforeEach.
 *
 * Para evitar isso:
 * - initSession usa singleflight (initPromise)
 * - refreshSession não roda se já estiver refreshing
 * - SIGNED_IN redundante é ignorado quando estado já está consistente
 *
 * NÃO remover esses controles sem entender o fluxo completo
 * entre sessão, guard e carregamento de tenant/entitlements.
 */

export const sessionUser = ref(null);
export const sessionRole = ref(null);
export const sessionIsSaasAdmin = ref(false);

// só no primeiro boot
export const sessionReady = ref(false);

// refresh leve (troca de aba / refresh token) sem desmontar UI
export const sessionRefreshing = ref(false);

let onSignedOutCallback = null;
export function setOnSignedOut(cb) {
    onSignedOutCallback = typeof cb === 'function' ? cb : null;
}

// V#2: session.js é o único registrante de supabase.auth.onAuthStateChange.
// Outros módulos (guards.js, etc.) se inscrevem aqui via onSessionEvent
// em vez de registrar listeners próprios.
const eventHandlers = {
    SIGNED_IN: [],
    SIGNED_OUT: [],
    TOKEN_REFRESHED: [],
    USER_UPDATED: []
};

/**
 * Inscreve handler para um evento de auth processado por listenAuthChanges.
 * Retorna função pra desregistrar.
 *
 * @param {'SIGNED_IN'|'SIGNED_OUT'|'TOKEN_REFRESHED'|'USER_UPDATED'} event
 * @param {(session: object|null) => void} handler
 */
export function onSessionEvent(event, handler) {
    if (!eventHandlers[event] || typeof handler !== 'function') return () => {};
    eventHandlers[event].push(handler);
    return () => {
        eventHandlers[event] = eventHandlers[event].filter((h) => h !== handler);
    };
}

function dispatch(event, sess) {
    const handlers = eventHandlers[event];
    if (!handlers || !handlers.length) return;
    for (const h of handlers) {
        try { h(sess); } catch (e) { logError('session', `${event} handler failed`, e); }
    }
}

// evita init concorrente
let initPromise = null;

async function fetchRole(userId) {
    const { data, error } = await supabase.from('profiles').select('role').eq('id', userId).maybeSingle();

    if (error) return null;
    return data?.role || null;
}

async function fetchIsSaasAdmin(userId) {
    const { data, error } = await supabase.from('saas_admins').select('user_id').eq('user_id', userId).maybeSingle();

    if (error) return false;
    return !!data;
}

/**
 * Atualiza estado a partir de uma session "confiável" (getSession() ou callback do auth).
 * ⚠️ NÃO zera user/role durante refresh enquanto existir sessão.
 */
async function hydrateFromSession(sess) {
    const user = sess?.user || null;
    if (!user?.id) return false;

    const prevUid = sessionUser.value?.id || null;
    const uid = user.id;

    // ✅ pega primeiro hydrate e troca de usuário
    const userChanged = prevUid !== uid;

    // atualiza user imediatamente (sem flicker)
    sessionUser.value = user;

    // ✅ saas admin: calcula no primeiro hydrate e sempre que trocar de user
    // (no primeiro hydrate prevUid é null, então userChanged = true)
    if (userChanged) {
        sessionIsSaasAdmin.value = await fetchIsSaasAdmin(uid);
    }

    // role: busca se não tem, ou se mudou user
    if (!sessionRole.value || userChanged) {
        sessionRole.value = await fetchRole(uid);
    }

    return true;
}

/**
 * Boot inicial (pode bloquear UI) ou refresh (não pode derrubar menu).
 */
export async function initSession({ initial = false } = {}) {
    if (initPromise) return initPromise;

    if (initial) sessionReady.value = false;
    else sessionRefreshing.value = true;

    initPromise = (async () => {
        try {
            const { data, error } = await supabase.auth.getSession();
            if (error) throw error;

            const sess = data?.session || null;
            const ok = await hydrateFromSession(sess);

            // se não tem sessão, zera estado (aqui pode, porque é init/refresh controlado)
            if (!ok) {
                sessionUser.value = null;
                sessionRole.value = null;
                sessionIsSaasAdmin.value = false;
            }
        } catch (e) {
            logError('session', 'initSession.getSession falhou (tratando como sem sessão)', e);
            // não deixa estourar pro router guard
            sessionUser.value = null;
            sessionRole.value = null;
            sessionIsSaasAdmin.value = false;
        }
    })();

    try {
        await initPromise;
    } finally {
        initPromise = null;
        if (initial) sessionReady.value = true;
        sessionRefreshing.value = false;
    }
}

// refresh leve (troca de aba etc.)
export async function refreshSession() {
    // ✅ evita corrida: se já está refreshing/init, não dispara outro
    if (sessionRefreshing.value || initPromise) return;

    const { data, error } = await supabase.auth.getSession();
    if (error) return;

    const sess = data?.session || null;
    const uid = sess?.user?.id || null;

    // se não tem sessão, não zera aqui (deixa SIGNED_OUT cuidar)
    if (!uid) return;

    // se já está consistente, não faz nada
    if (sessionUser.value?.id === uid && sessionRole.value) return;

    await initSession({ initial: false });
}

// evita múltiplos listeners
let authSubscription = null;

export function listenAuthChanges() {
    if (authSubscription) return;

    const { data } = supabase.auth.onAuthStateChange(async (event, sess) => {
        logAuth('event', { event });

        // ✅ SIGNED_OUT: zera e chama callback
        if (event === 'SIGNED_OUT') {
            sessionUser.value = null;
            sessionRole.value = null;
            sessionIsSaasAdmin.value = false;
            sessionRefreshing.value = false;
            sessionReady.value = true;
            if (onSignedOutCallback) onSignedOutCallback();
            dispatch('SIGNED_OUT', sess);
            return;
        }

        // ✅ se já está consistente, ignora SIGNED_IN redundante
        if (event === 'SIGNED_IN') {
            const uid = sess?.user?.id || null;
            if (uid && sessionReady.value && sessionUser.value?.id === uid && sessionRole.value) {
                dispatch('SIGNED_IN', sess);
                return;
            }
        }

        // ✅ use a session fornecida no callback
        if (sess?.user?.id) {
            // evita reentrância
            if (sessionRefreshing.value) return;

            sessionRefreshing.value = true;
            try {
                await hydrateFromSession(sess);
                sessionReady.value = true;
            } catch (e) {
                logError('session', 'auth hydrate error', e);
            } finally {
                sessionRefreshing.value = false;
            }
            dispatch(event === 'TOKEN_REFRESHED' ? 'TOKEN_REFRESHED' : 'SIGNED_IN', sess);
            return;
        }

        // fallback: refresh leve
        try {
            await refreshSession();
        } catch (e) {
            logError('session', 'refreshSession error', e);
        }
    });

    authSubscription = data?.subscription || null;
}

export function stopAuthChanges() {
    if (authSubscription) {
        authSubscription.unsubscribe();
        authSubscription = null;
    }
}
