import {
  ReactNode,
  createContext,
  useContext,
  useEffect,
  useState,
  useRef,
} from "react";
import * as WebBrowser from "expo-web-browser";

import { getApiUrl, baseApi } from "@/store/api/baseApi";
import { getToken, setToken, loadPersistedToken } from "@/auth/token";
import { store } from "@/store/index";
import { User } from "@/store/models/User.model";

WebBrowser.maybeCompleteAuthSession();

type AuthContextType = {
  user: User | null;
  token: string | null;
  loading: boolean;
  bootstrapping: boolean;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  register: (p: {
    firstName: string;
    lastName: string;
    email: string;
    password: string;
  }) => Promise<void>;
  logout: () => Promise<void>;
};

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

export const useAuthContext = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuthContext must be used within AuthProvider");
  return ctx;
};

export type AuthProviderProps = {
  children: ReactNode;
};

export const AuthProvider = ({ children }: AuthProviderProps) => {
  const [user, setUser] = useState<User | null>(null);
  const [tokenState, setTokenState] = useState<string | null>(getToken());
  const [bootstrapping, setBootstrapping] = useState(true);
  const [loading, setLoading] = useState(false);
  const loginInFlightRef = useRef(false);
  const loginPromiseRef = useRef<Promise<void> | null>(null);

  const isAuthenticated = !!tokenState && !!user;

  const syncUser = async (jwt?: string | null) => {
    const useToken = jwt ?? tokenState;
    if (!useToken) return;
    try {
      const res = await fetch(`${getApiUrl()?.replace(/\/$/, "")}/auth/me`, {
        headers: { Authorization: `Bearer ${useToken}` },
      });
      if (!res.ok) throw new Error("Failed to fetch current user");
      const u: User = await res.json();
      setUser(u);
    } catch (e) {
      console.warn("/auth/me failed:", e);
    }
  };

  useEffect(() => {
    (async () => {
      try {
        const t = await loadPersistedToken();
        setTokenState(t);
        if (t) await syncUser(t);
      } finally {
        setBootstrapping(false);
      }
    })();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const login: AuthContextType["login"] = async (email, password) => {
    if (loginInFlightRef.current && loginPromiseRef.current) {
      return await loginPromiseRef.current;
    }

    loginInFlightRef.current = true;
    setLoading(true);

    const p = (async () => {
      try {
        const res = await fetch(`${getApiUrl()?.replace(/\/$/, "")}/auth/login`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password }),
        });
        if (!res.ok) {
          const status = res.status;
          let msg = "Invalid credentials";
          let serverMsg: string | undefined;
          try {
            const errData = await res.json();
            serverMsg = errData?.message || errData?.error;
          } catch {}

          if (status === 403 && serverMsg && /not activated/i.test(serverMsg)) {
            msg =
              "Account is not activated. Please use your invitation link to set a password.";
          } else if (status >= 400 && status < 500) {
            msg = serverMsg || msg;
          } else {
            // For 5xx, keep default 'Invalid credentials' to avoid leaking internals
            msg = "Invalid credentials";
          }
          throw new Error(msg);
        }
        const data = await res.json();
        await setToken(data.token);
        setTokenState(data.token);
        setUser(data.user);
        // Clear/refresh caches after login for correct user context
        store.dispatch(baseApi.util.resetApiState());
        store.dispatch(baseApi.util.invalidateTags(["Users", "Todos"]));
      } finally {
        setLoading(false);
        loginInFlightRef.current = false;
        loginPromiseRef.current = null;
      }
    })();

    loginPromiseRef.current = p;
    return await p;
  };

  const register: AuthContextType["register"] = async ({
    firstName,
    lastName,
    email,
    password,
  }) => {
    setLoading(true);
    try {
      const res = await fetch(
        `${getApiUrl()?.replace(/\/$/, "")}/auth/register`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ firstName, lastName, email, password }),
        }
      );
      if (!res.ok) throw new Error("Registration failed");
      const data = await res.json();
      await setToken(data.token);
      setTokenState(data.token);
      setUser(data.user);
      // Invalidate users cache to refresh activation statuses after register
      store.dispatch(baseApi.util.invalidateTags(["Users"]));
    } finally {
      setLoading(false);
    }
  };

  const logout: AuthContextType["logout"] = async () => {
    await setToken(null);
    setTokenState(null);
    setUser(null);
    // Clear RTK Query caches on logout to prevent stale cross-user data
    store.dispatch(baseApi.util.resetApiState());
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        token: tokenState,
        loading,
        bootstrapping,
        isAuthenticated,
        login,
        register,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};
