import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from 'react';
import { login as apiLogin, logout as apiLogout, fetchMe } from '@/api/auth';
import { getToken, registerUnauthorizedHandler, setToken } from '@/api/client';
import type { Role, User } from '@/api/types';
import { useToast } from './useToast';

interface AuthContextValue {
  user: User | null;
  status: 'loading' | 'authenticated' | 'unauthenticated';
  login: (username: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  isAdmin: boolean;
  isRedteam: boolean;
  isSoc: boolean;
  canEditEngagements: boolean;
}

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

export function AuthProvider({ children }: { children: ReactNode }): JSX.Element {
  const [user, setUser] = useState<User | null>(null);
  const [status, setStatus] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');
  const { push } = useToast();

  // Bootstrap: if a token exists in storage, try to recover the session.
  useEffect(() => {
    const token = getToken();
    if (!token) {
      setStatus('unauthenticated');
      return;
    }
    let cancelled = false;
    fetchMe()
      .then((u) => {
        if (cancelled) return;
        setUser(u);
        setStatus('authenticated');
      })
      .catch(() => {
        // 401 interceptor already purges token + triggers redirect/toast.
        if (cancelled) return;
        setUser(null);
        setStatus('unauthenticated');
      });
    return () => {
      cancelled = true;
    };
  }, []);

  // Register the global 401 handler once.
  useEffect(() => {
    registerUnauthorizedHandler(() => {
      setUser(null);
      setStatus('unauthenticated');
      push('Session expirée', 'error');
      // Hard redirect so any in-flight query state is cleared.
      if (window.location.pathname !== '/login') {
        window.location.assign('/login');
      }
    });
  }, [push]);

  const login = useCallback(async (username: string, password: string) => {
    const resp = await apiLogin(username, password);
    setToken(resp.access_token);
    // Hydrate full user (with created_at) via /me.
    const me = await fetchMe();
    setUser(me);
    setStatus('authenticated');
  }, []);

  const logout = useCallback(async () => {
    try {
      await apiLogout();
    } catch {
      // Logout is best-effort server-side; the client purge below is what matters.
    }
    setToken(null);
    setUser(null);
    setStatus('unauthenticated');
  }, []);

  const value = useMemo<AuthContextValue>(() => {
    const role: Role | undefined = user?.role;
    return {
      user,
      status,
      login,
      logout,
      isAdmin: role === 'admin',
      isRedteam: role === 'redteam',
      isSoc: role === 'soc',
      canEditEngagements: role === 'admin' || role === 'redteam',
    };
  }, [user, status, login, logout]);

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

export function useAuth(): AuthContextValue {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
  return ctx;
}
