import { api, onUnauthorized, watchLegacyFetchForExpiry } from '@/api/client';
import { config } from '@/config';
import type { CurrentUser, PermissionName } from '@/auth/types';
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';

type AuthState = {
    user: CurrentUser | null;
    /** Solange false, ist noch nicht entschieden, ob jemand angemeldet ist. */
    ready: boolean;
    signIn: (username: string, password: string) => Promise<void>;
    signOut: () => Promise<void>;
    can: (permission: PermissionName) => boolean;
};

const AuthContext = createContext<AuthState | null>(null);

/**
 * Der Auth-Gate der Oberflaeche.
 *
 * Es wird nichts geraten: beim Start fragt die Anwendung /api/auth/me. 200
 * bedeutet angemeldet, 401 bedeutet Anmeldemaske. Danach halten zwei Uhren die
 * Sitzung im Griff -- eine verlaengert sie, solange jemand arbeitet, die andere
 * meldet nach einer Stunde ohne Eingabe ab.
 */
export function AuthProvider({ children }: { children: ReactNode }) {
    const [user, setUser] = useState<CurrentUser | null>(null);
    const [ready, setReady] = useState(false);
    const lastActivity = useRef(Date.now());

    const probe = useCallback(async () => {
        try {
            const data = await api.get<{ user: CurrentUser | null }>('/api/auth/me');
            setUser(data.user);
        } catch {
            // 401 und "Backend nicht erreichbar" fuehren beide zur Anmeldemaske.
            setUser(null);
        } finally {
            setReady(true);
        }
    }, []);

    useEffect(() => {
        void probe();
    }, [probe]);

    // Meldet das Backend irgendwo 401, faellt die Oberflaeche sofort zurueck.
    // Der Aufsatz erfasst dabei auch die direkten fetch-Aufrufe in Dashboard.tsx.
    useEffect(() => {
        watchLegacyFetchForExpiry();

        return onUnauthorized(() => setUser(null));
    }, []);

    // Jede Eingabe zaehlt als Aktivitaet.
    useEffect(() => {
        const note = () => {
            lastActivity.current = Date.now();
        };

        const events = ['pointerdown', 'keydown', 'wheel', 'touchstart'] as const;
        events.forEach((event) => window.addEventListener(event, note, { passive: true }));

        return () => events.forEach((event) => window.removeEventListener(event, note));
    }, []);

    // Sitzung verlaengern, solange gearbeitet wird; sonst abmelden.
    useEffect(() => {
        if (!user) return;

        const timer = window.setInterval(() => {
            if (Date.now() - lastActivity.current > config.session.idleMs) {
                void api.post('/api/auth/logout').catch(() => undefined);
                setUser(null);
                return;
            }

            void api
                .post<{ user: CurrentUser | null }>('/api/auth/refresh')
                .then((data) => setUser(data.user))
                .catch(() => setUser(null));
        }, config.session.refreshMs);

        return () => window.clearInterval(timer);
    }, [user]);

    const signIn = useCallback(async (username: string, password: string) => {
        const data = await api.post<{ user: CurrentUser }>('/api/auth/login', { username, password });
        lastActivity.current = Date.now();
        setUser(data.user);
    }, []);

    const signOut = useCallback(async () => {
        await api.post('/api/auth/logout').catch(() => undefined);
        setUser(null);
    }, []);

    const value = useMemo<AuthState>(
        () => ({
            user,
            ready,
            signIn,
            signOut,
            can: (permission) => user?.permissions.includes(permission) ?? false,
        }),
        [user, ready, signIn, signOut],
    );

    return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth(): AuthState {
    const context = useContext(AuthContext);

    if (context === null) throw new Error('useAuth benötigt einen AuthProvider.');

    return context;
}
