'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { clearAuthSession, readAuthSession, setAuthSession, subscribeToAuthSession } from '@/lib/auth-storage';
import { authApi } from '@/lib/api';
import { ensureOfflineDbForTenant } from '@/lib/db';
import { getApiUrl } from '@/lib/runtime-api';
import { syncEngine } from '@/lib/sync-engine';

interface User {
    id: number;
    username: string;
    nombre: string;
    rol: string;
    tiendaId?: number | null;
    permissions?: Record<string, 'none' | 'read' | 'write'>;
    // null/ausente = sin restricción en ambos -- ver comentario de las
    // columnas equivalentes en el backend (usuario.entity.ts).
    facturacionPosTiendaIds?: number[] | null;
    estadosVisibles?: string[] | null;
    tenantId?: number;
    rolPlataforma?: 'ninguno' | 'admin_plataforma';
}

export interface AvailableTenant {
    tenantId: number;
    codigo: string;
    nombre: string;
    entorno: 'demo' | 'produccion';
    via: 'tenant' | 'plataforma';
    current: boolean;
}

interface AuthContextType {
    user: User | null;
    token: string | null;
    login: (email: string, password: string) => Promise<void>;
    logout: () => void;
    isLoading: boolean;
    canRead: (module: string) => boolean;
    canWrite: (module: string) => boolean;
    isPlatformAdmin: boolean;
    tenants: AvailableTenant[];
    tenantsLoading: boolean;
    refreshTenants: () => Promise<void>;
    switchTenant: (tenantCodigo: string) => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

// Las 6 claves "configuracion.<grupo>" espejan uno a uno los grupos reales
// del catalogo de taller-parametros (backend: taller-parametros.catalogo.ts,
// campo `grupo`) -- mismo criterio y mismos valores que
// backend/src/auth/default-permissions.ts (DUPLICADO a propósito: frontend y
// backend no comparten runtime hoy).
const DEFAULT_PERMISSIONS_BY_ROLE: Record<string, Record<string, 'none' | 'read' | 'write'>> = {
    super_admin: {
        usuarios: 'write',
        'configuracion.facturacion': 'write', 'configuracion.inventario': 'write', 'configuracion.productos': 'write',
        'configuracion.tiendas': 'write', 'configuracion.notificaciones': 'write',
        'configuracion.operacion': 'write', confirmarPago: 'write',
        clientes: 'write', vehiculos: 'write', ots: 'write', catalogos: 'write',
        productos: 'write', historial: 'write', logs: 'write', cotizaciones: 'write', uploads: 'write', sync: 'write', reportes: 'write',
    },
    admin: {
        usuarios: 'read',
        'configuracion.facturacion': 'write', 'configuracion.inventario': 'write', 'configuracion.productos': 'write',
        'configuracion.tiendas': 'write', 'configuracion.notificaciones': 'write',
        'configuracion.operacion': 'write', confirmarPago: 'write',
        clientes: 'write', vehiculos: 'write', ots: 'write', catalogos: 'write',
        productos: 'write', historial: 'read', logs: 'read', cotizaciones: 'write', uploads: 'write', sync: 'write', reportes: 'write',
    },
    operativo: {
        usuarios: 'none',
        'configuracion.facturacion': 'none', 'configuracion.inventario': 'none', 'configuracion.productos': 'none',
        'configuracion.tiendas': 'none', 'configuracion.notificaciones': 'none',
        'configuracion.operacion': 'none', confirmarPago: 'none',
        clientes: 'read', vehiculos: 'read', ots: 'write', catalogos: 'read',
        productos: 'read', historial: 'read', logs: 'none', cotizaciones: 'write', uploads: 'write', sync: 'write', reportes: 'read',
    },
    recepcion: {
        usuarios: 'none',
        'configuracion.facturacion': 'none', 'configuracion.inventario': 'none', 'configuracion.productos': 'none',
        'configuracion.tiendas': 'none', 'configuracion.notificaciones': 'none',
        'configuracion.operacion': 'none', confirmarPago: 'none',
        clientes: 'read', vehiculos: 'read', ots: 'write', catalogos: 'read',
        productos: 'read', historial: 'read', logs: 'none', cotizaciones: 'write', uploads: 'write', sync: 'write', reportes: 'read',
    },
    mecanico: {
        usuarios: 'none',
        'configuracion.facturacion': 'none', 'configuracion.inventario': 'none', 'configuracion.productos': 'none',
        'configuracion.tiendas': 'none', 'configuracion.notificaciones': 'none',
        'configuracion.operacion': 'none', confirmarPago: 'none',
        clientes: 'read', vehiculos: 'read', ots: 'write', catalogos: 'read',
        productos: 'read', historial: 'read', logs: 'none', cotizaciones: 'write', uploads: 'write', sync: 'write', reportes: 'none',
    },
};

function resolvePermissions(user: User | null) {
    if (!user) return {};
    return {
        ...(DEFAULT_PERMISSIONS_BY_ROLE[user.rol] || DEFAULT_PERMISSIONS_BY_ROLE.operativo),
        ...(user.permissions || {}),
    };
}

export function useAuth() {
    const context = useContext(AuthContext);
    if (!context) {
        throw new Error('useAuth must be used within AuthProvider');
    }
    return context;
}

interface OfflineContextType {
    isOnline: boolean;
    pendingSync: number;
}

const OfflineContext = createContext<OfflineContextType>({
    isOnline: true,
    pendingSync: 0,
});

async function readLoginResponse(response: Response) {
    const text = await response.text().catch(() => '');

    if (!text) {
        return null;
    }

    try {
        return JSON.parse(text);
    } catch {
        return null;
    }
}

async function getLoginErrorMessage(response: Response) {
    const data = await readLoginResponse(response);
    const message = Array.isArray(data?.message) ? data.message.join(' ') : data?.message;

    if (message) {
        return message;
    }

    if (response.status === 404) {
        return 'No se pudo conectar con el sistema. Revisá tu conexión a internet e intentá de nuevo.';
    }

    if (response.status >= 500) {
        return 'El sistema no respondió. Intentá de nuevo en un momento; si sigue igual, avisá a soporte.';
    }

    return 'No se pudo iniciar sesión. Revisá el correo y la contraseña.';
}

export function useOffline() {
    return useContext(OfflineContext);
}

export function Providers({ children }: { children: React.ReactNode }) {
    const [queryClient] = useState(
        () =>
            new QueryClient({
                defaultOptions: {
                    queries: {
                        staleTime: 60 * 1000,
                        retry: 1,
                    },
                },
            })
    );

    const [user, setUser] = useState<User | null>(null);
    const [token, setToken] = useState<string | null>(null);
    const [isLoading, setIsLoading] = useState(true);
    const [isOnline, setIsOnline] = useState(syncEngine.getStatus().isOnline);
    const [pendingSync, setPendingSync] = useState(syncEngine.getStatus().pendingCount);
    const [tenants, setTenants] = useState<AvailableTenant[]>([]);
    const [tenantsLoading, setTenantsLoading] = useState(false);

    const refreshTenants = useCallback(async () => {
        if (!readAuthSession().accessToken) {
            setTenants([]);
            return;
        }
        setTenantsLoading(true);
        try {
            const list = await authApi.tenantsDisponibles();
            setTenants(list);
        } catch {
            // Silencioso a proposito: el switcher simplemente no ofrece opciones
            // si esto falla (ej. offline) — no debe tumbar el resto del dashboard.
            setTenants([]);
        } finally {
            setTenantsLoading(false);
        }
    }, []);

    useEffect(() => {
        const syncSession = () => {
            const session = readAuthSession();
            setToken(session.accessToken);
            setUser(session.user);
            setIsLoading(false);
        };

        syncSession();
        void refreshTenants();
        const unsubscribe = subscribeToAuthSession(syncSession);

        return unsubscribe;
    }, [refreshTenants]);

    useEffect(() => {
        const unsubscribe = syncEngine.subscribe((snapshot) => {
            setIsOnline(snapshot.isOnline);
            setPendingSync(snapshot.pendingCount);
        });

        void syncEngine.refreshStatus();

        return unsubscribe;
    }, []);

    const login = async (email: string, password: string) => {
        const response = await fetch(`${getApiUrl()}/auth/login`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            // El backend acepta email O username local bajo el mismo campo
            // "username" (LoginDto) — el frontend ahora manda siempre el
            // correo, pero el nombre del campo en el body no cambia para no
            // tocar el contrato del DTO.
            body: JSON.stringify({ username: email, password }),
        });

        if (!response.ok) {
            throw new Error(await getLoginErrorMessage(response));
        }

        const data = await readLoginResponse(response);

        if (!data?.accessToken || !data?.user) {
            throw new Error('El servidor respondió sin datos de inicio de sesión.');
        }

        // El cache offline (IndexedDB) es UNO solo por navegador, sin noción
        // de tenant — si este dispositivo ya tenía datos de OTRO cliente,
        // esto los vacía antes de dejar entrar al nuevo (o corta el login
        // con un error claro si hay trabajo offline sin sincronizar todavía,
        // en vez de arriesgarse a perderlo o mezclarlo).
        if (typeof data.user.tenantId === 'number') {
            await ensureOfflineDbForTenant(data.user.tenantId);
        }

        setToken(data.accessToken);
        setUser(data.user);
        setAuthSession({
            accessToken: data.accessToken,
            refreshToken: data.refreshToken,
            user: data.user,
        });
        void refreshTenants();
    };

    const logout = () => {
        setToken(null);
        setUser(null);
        setTenants([]);
        clearAuthSession({ redirectToLogin: true });
    };

    // Cambiar de tenant SIEMPRE reemite el token contra el backend — nunca es
    // un cambio "solo del lado del cliente". cancelQueries() ANTES de guardar
    // el token nuevo evita que una consulta en vuelo (lanzada con el token
    // viejo, contra el tenant viejo) resuelva DESPUES del cambio y pinte por
    // un instante datos del tenant anterior; clear() DESPUES tira todo el
    // cache de React Query para que la primera pantalla del tenant nuevo
    // nunca muestre nada heredado del anterior.
    //
    // ensureOfflineDbForTenant() es la mitad que faltaba, encontrada viendo
    // el bug real en producción: React Query solo cubre el cache EN MEMORIA
    // — el cache offline (IndexedDB, para poder trabajar sin conexión) es
    // otra base completamente aparte, sin noción de tenant, que
    // queryClient.clear() nunca toca. Sin esto, cambiar de cliente seguía
    // mostrando las OTs/clientes del anterior.
    const switchTenant = async (tenantCodigo: string) => {
        await queryClient.cancelQueries();
        const data = await authApi.switchTenant(tenantCodigo);

        if (!data?.accessToken || !data?.user) {
            throw new Error('No se pudo cambiar de cliente. Cerrá sesión y volvé a entrar.');
        }

        if (typeof data.user.tenantId === 'number') {
            await ensureOfflineDbForTenant(data.user.tenantId);
        }

        setToken(data.accessToken);
        setUser(data.user);
        setAuthSession({
            accessToken: data.accessToken,
            refreshToken: data.refreshToken,
            user: data.user,
        });
        queryClient.clear();
        await refreshTenants();
    };

    const canRead = (module: string) => {
        const level = resolvePermissions(user)[module] || 'none';
        return level === 'read' || level === 'write';
    };

    const canWrite = (module: string) => {
        const level = resolvePermissions(user)[module] || 'none';
        return level === 'write';
    };

    const isPlatformAdmin = user?.rolPlataforma === 'admin_plataforma';

    return (
        <QueryClientProvider client={queryClient}>
            <AuthContext.Provider
                value={{
                    user,
                    token,
                    login,
                    logout,
                    isLoading,
                    canRead,
                    canWrite,
                    isPlatformAdmin,
                    tenants,
                    tenantsLoading,
                    refreshTenants,
                    switchTenant,
                }}
            >
                <OfflineContext.Provider value={{ isOnline, pendingSync }}>
                    {children}
                </OfflineContext.Provider>
            </AuthContext.Provider>
        </QueryClientProvider>
    );
}
