import * as SecureStore from "expo-secure-store";
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";

import { setAuthToken, clearAuthToken } from "./fetch";

const TOKEN_KEY = "waseel_auth_token";
const USER_KEY = "waseel_auth_user";
const DEFAULT_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;

export type SessionUser = {
  id: string;
  name: string;
  email: string;
  role?: string | null;
  avatarUrl?: string | null;
};

type AuthResult = { token: string; user: SessionUser };

type SessionContextValue = {
  isLoaded: boolean;
  isSignedIn: boolean;
  userId: string | null;
  user: SessionUser | null;
  setSession: (result: AuthResult) => Promise<void>;
  setUserRole: (role: string) => void;
  signOut: () => Promise<void>;
};

const SessionContext = createContext<SessionContextValue | null>(null);

// Mirrors lib/jwt.ts payload decoding (no signature check needed client-side).
export const decodeJwtExp = (token: string): number | null => {
  try {
    const claims = JSON.parse(
      atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")),
    );
    return typeof claims.exp === "number" ? claims.exp : null;
  } catch {
    return null;
  }
};

export const SessionProvider = ({ children }: { children: ReactNode }) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const [user, setUser] = useState<SessionUser | null>(null);

  useEffect(() => {
    let cancelled = false;

    const restore = async () => {
      try {
        const [token, storedUser] = await Promise.all([
          SecureStore.getItemAsync(TOKEN_KEY),
          SecureStore.getItemAsync(USER_KEY),
        ]);

        if (!token || !storedUser) return;

        const exp = decodeJwtExp(token) ?? 0;
        if (exp * 1000 < Date.now()) {
          await SecureStore.deleteItemAsync(TOKEN_KEY);
          await SecureStore.deleteItemAsync(USER_KEY);
          return;
        }

        if (cancelled) return;

        setAuthToken(token);
        setUser(JSON.parse(storedUser) as SessionUser);
      } catch (error) {
        console.error("[SESSION_RESTORE]: ", error);
      } finally {
        if (!cancelled) setIsLoaded(true);
      }
    };

    void restore();

    return () => {
      cancelled = true;
    };
  }, []);

  const setSession = useCallback(async (result: AuthResult) => {
    setAuthToken(result.token);
    setUser(result.user);

    await SecureStore.setItemAsync(TOKEN_KEY, result.token);
    await SecureStore.setItemAsync(USER_KEY, JSON.stringify(result.user));
  }, []);

  const setUserRole = useCallback(
    (role: string) => {
      setUser((currentUser) => {
        if (!currentUser) return currentUser;

        const updated = { ...currentUser, role };

        void SecureStore.setItemAsync(USER_KEY, JSON.stringify(updated));

        return updated;
      });
    },
    [],
  );

  const signOut = useCallback(async () => {
    clearAuthToken();
    setUser(null);

    await SecureStore.deleteItemAsync(TOKEN_KEY);
    await SecureStore.deleteItemAsync(USER_KEY);
  }, []);

  const value = useMemo<SessionContextValue>(
    () => ({
      isLoaded,
      isSignedIn: user !== null,
      userId: user?.id ?? null,
      user,
      setSession,
      setUserRole,
      signOut,
    }),
    [isLoaded, user, setSession, setUserRole, signOut],
  );

  return (
    <SessionContext.Provider value={value}>
      {children}
    </SessionContext.Provider>
  );
};

export const useSession = (): SessionContextValue => {
  const context = useContext(SessionContext);

  if (!context) {
    throw new Error("useSession must be used within a SessionProvider.");
  }

  return context;
};

// Alias kept for parity with the previous auth API.
export const useAuth = useSession;

export const TOKEN_TTL_SECONDS = DEFAULT_TOKEN_TTL_SECONDS;
