import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import * as authApi from "../api/auth";
import { setTokenProvider, setUnauthorizedHandler } from "../api/client";
import type { Permission, Role, User } from "../api/types";
import { AuthContext, type AuthState } from "./AuthContext";

const STORAGE_KEY = "gc.token";

/**
 * Token storage policy: the source of truth is an in-memory ref (survives
 * re-renders, never serialized into React state to avoid accidental logging).
 * It is mirrored into sessionStorage — NOT localStorage — so it clears when the
 * tab closes and never leaks across browser sessions.
 */
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const tokenRef = useRef<string | null>(sessionStorage.getItem(STORAGE_KEY));
  const [user, setUser] = useState<User | null>(null);
  const [initializing, setInitializing] = useState(true);

  const setToken = useCallback((token: string | null) => {
    tokenRef.current = token;
    if (token) sessionStorage.setItem(STORAGE_KEY, token);
    else sessionStorage.removeItem(STORAGE_KEY);
  }, []);

  // Wire the API client to read our token and to notify us on 401.
  useEffect(() => {
    setTokenProvider(() => tokenRef.current);
  }, []);

  const clearSession = useCallback(() => {
    setToken(null);
    setUser(null);
  }, [setToken]);

  useEffect(() => {
    setUnauthorizedHandler(clearSession);
    return () => setUnauthorizedHandler(null);
  }, [clearSession]);

  // Restore session on first load if a token is present.
  useEffect(() => {
    let cancelled = false;
    async function restore() {
      if (!tokenRef.current) {
        setInitializing(false);
        return;
      }
      try {
        const me = await authApi.getMe();
        if (!cancelled) setUser(me);
      } catch {
        // Invalid/expired token — clear it. The 401 handler also fires, but
        // guard here for non-401 failures too.
        if (!cancelled) clearSession();
      } finally {
        if (!cancelled) setInitializing(false);
      }
    }
    void restore();
    return () => {
      cancelled = true;
    };
  }, [clearSession]);

  const login = useCallback(
    async (username: string, password: string) => {
      const res = await authApi.login({ username, password });
      setToken(res.token);
      setUser(res.user);
    },
    [setToken],
  );

  const logout = useCallback(async () => {
    try {
      // Best-effort server-side revocation; clear locally regardless.
      await authApi.logout();
    } catch {
      // ignore — token may already be invalid
    } finally {
      clearSession();
    }
  }, [clearSession]);

  const value = useMemo<AuthState>(() => {
    const role = user?.role;
    return {
      user,
      initializing,
      login,
      logout,
      isAdmin: role === "admin",
      hasRole: (r: Role) => role === r,
      hasPermission: (p: Permission) => user?.permissions.includes(p) ?? false,
    };
  }, [user, initializing, login, logout]);

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