import React, { createContext, useCallback, useEffect, useMemo, useState, } from "react"; import toast from "react-hot-toast"; import { authService, userService } from "../services"; // Tipos auxiliares interface UserInfoFullResponse { access_token: string; refresh_token: string; user: { id: string; email?: string; user_metadata?: any; }; roles?: string[]; permissions?: any; profile?: { full_name?: string; }; } // Mock temporário para compatibilidade const doctorService = { loginMedico: async (email: string, senha: string) => ({ success: false, error: "Use login unificado", data: null as any, }), }; type Medico = any; // tokenManager removido no modelo somente Supabase (sem usuário técnico) // Tipos de roles suportados export type UserRole = | "secretaria" | "medico" | "paciente" | "admin" | "gestor" | "user"; // Role genérica para pacientes export interface SessionUserBase { id: string; nome: string; email?: string; role: UserRole; roles?: UserRole[]; permissions?: { [k: string]: boolean | undefined }; } export interface SecretariaUser extends SessionUserBase { role: "secretaria"; } export interface MedicoUser extends SessionUserBase { role: "medico"; crm?: string; especialidade?: string; } export interface PacienteUser extends SessionUserBase { role: "paciente"; pacienteId?: string; } export interface AdminUser extends SessionUserBase { role: "admin"; } export type SessionUser = | SecretariaUser | MedicoUser | PacienteUser | AdminUser | (SessionUserBase & { role: "gestor" }); interface AuthContextValue { user: SessionUser | null; isAuthenticated: boolean; loading: boolean; loginSecretaria: (email: string, senha: string) => Promise; loginMedico: (email: string, senha: string) => Promise; loginComEmailSenha: (email: string, senha: string) => Promise; // fluxo unificado real loginPaciente: (paciente: { id: string; nome: string; email?: string; }) => Promise; logout: () => void; role: UserRole | null; roles: UserRole[]; permissions: Record; refreshSession: () => Promise; } const AuthContext = createContext(undefined); const STORAGE_KEY = "appSession"; interface PersistedSession { user: SessionUser; token?: string; // para quando integrar authService real refreshToken?: string; savedAt: string; } export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Log sempre que user ou loading mudar useEffect(() => { console.log("[AuthContext] 🔄 ESTADO MUDOU:", { user: user ? { id: user.id, nome: user.nome, role: user.role } : null, loading, isAuthenticated: !!user, timestamp: new Date().toISOString(), }); }, [user, loading]); // RE-VERIFICAR sessão quando user estiver null mas localStorage tiver dados // Isso corrige o problema de navegação entre páginas perdendo o estado useEffect(() => { if (!loading && !user) { console.log( "[AuthContext] 🔍 User é null mas loading false, verificando localStorage..." ); const raw = localStorage.getItem(STORAGE_KEY); if (raw) { try { const parsed = JSON.parse(raw) as PersistedSession; if (parsed?.user?.role) { console.log( "[AuthContext] 🔧 RECUPERANDO sessão perdida:", parsed.user.nome ); setUser(parsed.user); // Token restoration is handled automatically by authService } } catch (e) { console.error("[AuthContext] Erro ao recuperar sessão:", e); } } } }, [user, loading]); // Monitorar mudanças no localStorage para debug useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === STORAGE_KEY) { console.log("[AuthContext] 📢 localStorage MUDOU externamente!", { oldValue: e.oldValue ? "TINHA DADOS" : "VAZIO", newValue: e.newValue ? "TEM DADOS" : "VAZIO", url: e.url, }); } }; window.addEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange); }, []); // Restaurar sessão do localStorage e verificar token // IMPORTANTE: Este useEffect roda apenas UMA VEZ quando o AuthProvider monta useEffect(() => { console.log("[AuthContext] 🚀 INICIANDO RESTAURAÇÃO DE SESSÃO (mount)"); console.log("[AuthContext] 🔍 Verificando TODOS os itens no localStorage:"); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key) { const value = localStorage.getItem(key); console.log(` - ${key}: ${value?.substring(0, 50)}...`); } } const restoreSession = async () => { try { // Tentar localStorage primeiro, depois sessionStorage como backup let raw = localStorage.getItem(STORAGE_KEY); console.log( "[AuthContext] localStorage raw:", raw ? "EXISTE" : "VAZIO" ); if (!raw) { console.log( "[AuthContext] 🔍 localStorage vazio, tentando sessionStorage..." ); raw = sessionStorage.getItem(STORAGE_KEY); console.log( "[AuthContext] sessionStorage raw:", raw ? "EXISTE" : "VAZIO" ); if (raw) { // Restaurar do sessionStorage para localStorage console.log( "[AuthContext] 🔄 Restaurando do sessionStorage para localStorage" ); localStorage.setItem(STORAGE_KEY, raw); } } if (raw) { console.log( "[AuthContext] Conteúdo completo:", raw.substring(0, 100) + "..." ); } if (raw) { const parsed = JSON.parse(raw) as PersistedSession; if (parsed?.user?.role) { console.log("[AuthContext] ✅ Restaurando sessão:", { nome: parsed.user.nome, role: parsed.user.role, hasToken: !!parsed.token, }); // Token management is handled automatically by authService if (parsed.token) { console.log("[AuthContext] Sessão com token encontrada"); } else { console.warn( "[AuthContext] ⚠️ Sessão encontrada mas sem token. Pode estar inválida." ); } console.log( "[AuthContext] 📝 Chamando setUser com:", parsed.user.nome ); setUser(parsed.user); } else { console.log( "[AuthContext] ⚠️ Sessão parseada mas sem user.role válido" ); } } else { console.log( "[AuthContext] ℹ️ Nenhuma sessão salva encontrada no localStorage" ); } } catch (error) { console.error("[AuthContext] ❌ Erro ao restaurar sessão:", error); // Se houver erro ao restaurar, limpar tudo para evitar loops console.log("[AuthContext] 🧹 Limpando localStorage devido a erro"); localStorage.removeItem(STORAGE_KEY); sessionStorage.removeItem(STORAGE_KEY); } finally { console.log( "[AuthContext] 🏁 Finalizando restauração, setLoading(false)" ); setLoading(false); } }; void restoreSession(); }, []); const persist = useCallback((session: PersistedSession) => { try { console.log( "[AuthContext] 💾 SALVANDO sessão no localStorage E sessionStorage:", { user: session.user.nome, role: session.user.role, hasToken: !!session.token, } ); const sessionStr = JSON.stringify(session); localStorage.setItem(STORAGE_KEY, sessionStr); sessionStorage.setItem(STORAGE_KEY, sessionStr); // BACKUP em sessionStorage console.log( "[AuthContext] ✅ Sessão salva com sucesso em ambos storages!" ); } catch (error) { console.error("[AuthContext] ❌ ERRO ao salvar sessão:", error); } }, []); const clearPersisted = useCallback(() => { try { console.log( "[AuthContext] 🗑️ REMOVENDO sessão do localStorage E sessionStorage" ); localStorage.removeItem(STORAGE_KEY); sessionStorage.removeItem(STORAGE_KEY); console.log( "[AuthContext] ✅ Sessão removida com sucesso de ambos storages!" ); } catch (error) { console.error("[AuthContext] ❌ ERRO ao remover sessão:", error); } }, []); const normalizeRole = (r: string | undefined): UserRole | undefined => { if (!r) return undefined; const map: Record = { medico: "medico", doctor: "medico", secretaria: "secretaria", assistant: "secretaria", paciente: "paciente", patient: "paciente", user: "paciente", // Role genérica mapeada para paciente admin: "admin", gestor: "gestor", manager: "gestor", }; return map[r.toLowerCase()] || undefined; }; const pickPrimaryRole = (rolesArr: UserRole[]): UserRole => { const priority: UserRole[] = [ "admin", "gestor", "medico", "secretaria", "paciente", ]; for (const p of priority) if (rolesArr.includes(p)) return p; return rolesArr[0] || "paciente"; }; const buildSessionUser = React.useCallback( (info: UserInfoFullResponse): SessionUser => { // ⚠️ SEGURANÇA: Nunca logar tokens ou dados sensíveis em produção const rolesNormalized = (info.roles || []) .map(normalizeRole) .filter(Boolean) as UserRole[]; const permissions = info.permissions || {}; const primaryRole = pickPrimaryRole( rolesNormalized.length ? rolesNormalized : [normalizeRole((info.roles || [])[0]) || "paciente"] ); const base = { id: info.user?.id || "", nome: info.profile?.full_name || info.user?.email?.split("@")[0] || "Usuário", email: info.user?.email, role: primaryRole, roles: rolesNormalized, permissions, } as SessionUserBase; if (primaryRole === "medico") { return { ...base, role: "medico" } as MedicoUser; } if (primaryRole === "secretaria") { return { ...base, role: "secretaria" } as SecretariaUser; } if (primaryRole === "admin") { return { ...base, role: "admin" } as AdminUser; } if (primaryRole === "gestor") { return { ...base, role: "gestor" } as SessionUser; } return { ...base, role: "paciente" } as PacienteUser; }, [] ); // LEGADO: manter até que todos os usuários passem a existir no auth real const loginSecretaria = useCallback( async (email: string, senha: string) => { // Mock atual: validar contra credenciais fixas (pode evoluir para authService.login) if (email === "secretaria@clinica.com" && senha === "secretaria123") { const newUser: SecretariaUser = { id: "sec-1", nome: "Secretária", email, role: "secretaria", roles: ["secretaria"], permissions: {}, }; setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); return true; } toast.error("Credenciais inválidas"); return false; }, [persist] ); // LEGADO: usa service de médicos sem validar senha real (apenas existência) const loginMedico = useCallback( async (email: string, senha: string) => { const resp = await doctorService.loginMedico(email, senha); if (!resp.success || !resp.data) { toast.error(resp.error || "Erro ao autenticar médico"); return false; } const m: Medico = resp.data; const newUser: MedicoUser = { id: m.id, nome: m.nome, email: m.email, role: "medico", crm: m.crm, especialidade: m.especialidade, roles: ["medico"], permissions: {}, }; setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); toast.success(`Bem-vindo(a) Dr(a). ${m.nome}`); return true; }, [persist] ); // Fluxo unificado real usando authService const loginComEmailSenha = useCallback( async (email: string, senha: string) => { try { const loginResp = await authService.login({ email, password: senha }); // Fetch full user info with roles and permissions const userInfo = await userService.getUserInfo(); // Build session user from full user info const sessionUser = buildSessionUser({ access_token: loginResp.access_token, refresh_token: loginResp.refresh_token, user: userInfo.user, roles: userInfo.roles, permissions: userInfo.permissions, profile: userInfo.profile ? { full_name: userInfo.profile.full_name } : undefined, } as UserInfoFullResponse); setUser(sessionUser); persist({ user: sessionUser, savedAt: new Date().toISOString(), token: loginResp.access_token, refreshToken: loginResp.refresh_token, }); return true; } catch (error) { console.error("[AuthContext] Login falhou:", error); toast.error("Falha no login"); return false; } }, [persist, buildSessionUser] ); // Para paciente, aproveitamos fluxo existente: quando o paciente já foi validado externamente no loginPaciente const loginPaciente = useCallback( async (paciente: { id: string; nome: string; email?: string }) => { console.log("[AuthContext] loginPaciente chamado com:", paciente); const newUser: PacienteUser = { id: paciente.id, nome: paciente.nome, email: paciente.email, role: "paciente", pacienteId: paciente.id, roles: ["paciente"], permissions: {}, }; console.log("[AuthContext] Usuário criado:", newUser); setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); // Bridge para páginas que ainda leem localStorage("pacienteLogado") try { const legacy = { _id: paciente.id, nome: paciente.nome, email: paciente.email ?? "", cpf: "", telefone: "", }; localStorage.setItem("pacienteLogado", JSON.stringify(legacy)); } catch { // ignore } console.log("[AuthContext] Usuário persistido no localStorage"); toast.success(`Bem-vindo(a), ${paciente.nome}`); return true; }, [persist] ); const logout = useCallback(async () => { console.log("[AuthContext] Iniciando logout..."); try { await authService.logout(); // Returns void on success console.log("[AuthContext] Logout remoto bem-sucedido"); } catch (e) { console.warn( "[AuthContext] Erro ao executar logout remoto (continuando limpeza local)", e ); } finally { // Limpa contexto local console.log("[AuthContext] Limpando estado local..."); setUser(null); clearPersisted(); try { localStorage.removeItem("pacienteLogado"); } catch { // ignore } console.log("[AuthContext] Logout completo - usuário removido do estado"); } }, [clearPersisted]); const refreshSession = useCallback(async () => { // Futuro: usar refresh token real. Agora apenas revalida estrutura. try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; const parsed = JSON.parse(raw) as PersistedSession; if (!parsed?.user?.role) return; setUser(parsed.user); } catch { // ignorar } }, []); const value: AuthContextValue = useMemo( () => ({ user, role: user?.role ?? null, roles: user?.roles || (user?.role ? [user.role] : []), permissions: user?.permissions || {}, isAuthenticated: !!user, loading, loginSecretaria, loginMedico, loginComEmailSenha, loginPaciente, logout, refreshSession, }), [ user, loading, loginSecretaria, loginMedico, loginComEmailSenha, loginPaciente, logout, refreshSession, ] ); return {children}; }; export default AuthContext;