import {
  createContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";

import { useIdentify } from "@/lib/posthog";
import { getMeRequest, loginRequest, verifyMfaRequest } from "./api";
import {
  AUTH_TOKEN_COOKIE,
  AUTH_USER_COOKIE,
  clearSessionCookies,
  getCookie,
  setCookie,
} from "./cookies";
import { applyTokens } from "./http";
import {
  startTokenRefreshScheduler,
  stopTokenRefreshScheduler,
} from "./refreshScheduler";
import type { AuthTokens, AuthUser } from "./types";

interface LoginPayload {
  email: string;
  password: string;
}

interface VerifyMfaPayload {
  email: string;
  otp: string;
}

export interface AuthContextValue {
  user: AuthUser | null;
  loading: boolean;
  login: (payload: LoginPayload) => Promise<{ mfaRequired: boolean }>;
  verifyMfa: (payload: VerifyMfaPayload) => Promise<void>;
  logout: () => void;
}

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

const persistUser = (user: AuthUser | null) => {
  if (user) {
    setCookie(AUTH_USER_COOKIE, JSON.stringify(user));
    return;
  }

  clearSessionCookies();
};

const bootstrapCachedUser = (): AuthUser | null => {
  const serialized = getCookie(AUTH_USER_COOKIE);
  if (!serialized) {
    return null;
  }

  try {
    return JSON.parse(serialized) as AuthUser;
  } catch {
    return null;
  }
};

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<AuthUser | null>(() => bootstrapCachedUser());
  const [loading, setLoading] = useState(true);
  const mfaEmailRef = useRef<string | null>(null);

  // Attribute replays and exceptions to the signed-in user (id/org only).
  useIdentify(user);

  const loadCurrentUser = async () => {
    const currentUser = await getMeRequest();
    setUser(currentUser);
    persistUser(currentUser);
  };

  useEffect(() => {
    ["auth-token", "refresh-token"].forEach((name) => {
      const value = getCookie(name);
      if (value === "undefined" || value === "null" || value === "") {
        clearSessionCookies();
      }
    });

    const bootstrap = async () => {
      const token = getCookie(AUTH_TOKEN_COOKIE);
      if (!token) {
        setLoading(false);
        return;
      }

      try {
        await loadCurrentUser();
      } catch {
        clearSessionCookies();
        setUser(null);
      } finally {
        setLoading(false);
      }
    };

    void bootstrap();
  }, []);

  // Keep the server session alive while a user is logged in. Runs after
  // login, MFA verification, and page-reload bootstrap alike.
  useEffect(() => {
    if (!user) {
      stopTokenRefreshScheduler();
      return;
    }

    startTokenRefreshScheduler();
    return stopTokenRefreshScheduler;
  }, [user]);

  const value = useMemo<AuthContextValue>(
    () => ({
      user,
      loading,
      login: async ({ email, password }) => {
        const result = await loginRequest({ email, password });

        if (result.mfaRequired) {
          mfaEmailRef.current = email;
          return { mfaRequired: true };
        }

        const tokens = result as AuthTokens;
        applyTokens(tokens);
        await loadCurrentUser();
        mfaEmailRef.current = null;
        return { mfaRequired: false };
      },
      verifyMfa: async ({ email, otp }) => {
        const identifier = mfaEmailRef.current ?? email;
        const tokens = await verifyMfaRequest({ email: identifier, otp });
        applyTokens(tokens);
        await loadCurrentUser();
        mfaEmailRef.current = null;
      },
      logout: () => {
        const preservedTheme = window.localStorage.getItem("edr-theme");

        clearSessionCookies();
        window.localStorage.clear();

        if (preservedTheme === "dark" || preservedTheme === "light") {
          window.localStorage.setItem("edr-theme", preservedTheme);
        }

        setUser(null);
        window.location.replace("/auth");
      },
    }),
    [loading, user],
  );

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