import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import type { Session, User } from "@supabase/supabase-js";
import { fetchMe, type AccountInfo } from "./api";
import {
  getCachedAuthConfig,
  getSupabase,
  initSupabase,
  type PlanCard,
} from "./supabase";

const ACTIVITY_EVENTS = [
  "mousedown",
  "mousemove",
  "keydown",
  "scroll",
  "touchstart",
  "click",
  "visibilitychange",
] as const;

type AuthContextValue = {
  ready: boolean;
  authEnabled: boolean;
  session: Session | null;
  user: User | null;
  account: AccountInfo | null;
  plans: PlanCard[];
  guestMaxWords: number;
  sessionIdleMinutes: number;
  idleSignedOut: boolean;
  clearIdleNotice: () => void;
  refreshAccount: () => Promise<void>;
  setAccount: (account: AccountInfo | null) => void;
  signInWithPassword: (email: string, password: string) => Promise<void>;
  signUp: (email: string, password: string) => Promise<string>;
  signInWithGoogle: () => Promise<void>;
  signOut: () => Promise<void>;
};

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

function useIdleSessionLogout(
  enabled: boolean,
  idleMinutes: number,
  hasSession: boolean,
  onIdle: () => void,
) {
  const timerRef = useRef<number | null>(null);
  const onIdleRef = useRef(onIdle);
  onIdleRef.current = onIdle;

  useEffect(() => {
    if (!enabled || !hasSession || idleMinutes <= 0) {
      if (timerRef.current != null) {
        window.clearTimeout(timerRef.current);
        timerRef.current = null;
      }
      return;
    }

    const ms = idleMinutes * 60 * 1000;

    const bump = () => {
      if (document.visibilityState === "hidden") return;
      if (timerRef.current != null) window.clearTimeout(timerRef.current);
      timerRef.current = window.setTimeout(() => {
        onIdleRef.current();
      }, ms);
    };

    bump();
    for (const ev of ACTIVITY_EVENTS) {
      window.addEventListener(ev, bump, { passive: true });
    }
    return () => {
      if (timerRef.current != null) window.clearTimeout(timerRef.current);
      for (const ev of ACTIVITY_EVENTS) {
        window.removeEventListener(ev, bump);
      }
    };
  }, [enabled, hasSession, idleMinutes]);
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const [ready, setReady] = useState(false);
  const [authEnabled, setAuthEnabled] = useState(false);
  const [session, setSession] = useState<Session | null>(null);
  const [account, setAccount] = useState<AccountInfo | null>(null);
  const [plans, setPlans] = useState<PlanCard[]>([]);
  const [guestMaxWords, setGuestMaxWords] = useState(100);
  const [sessionIdleMinutes, setSessionIdleMinutes] = useState(30);
  const [idleSignedOut, setIdleSignedOut] = useState(false);

  const refreshAccount = useCallback(async () => {
    if (!authEnabled) {
      setAccount(null);
      return;
    }
    try {
      const me = await fetchMe(session?.access_token);
      setAccount(me.account);
    } catch {
      setAccount(null);
    }
  }, [authEnabled, session]);

  const signOut = useCallback(async () => {
    const sb = getSupabase();
    if (!sb) return;
    await sb.auth.signOut();
    setAccount(null);
  }, []);

  const handleIdleLogout = useCallback(async () => {
    setIdleSignedOut(true);
    await signOut();
  }, [signOut]);

  useIdleSessionLogout(
    authEnabled,
    sessionIdleMinutes,
    Boolean(session),
    () => {
      void handleIdleLogout();
    },
  );

  useEffect(() => {
    let unsub: (() => void) | undefined;
    void (async () => {
      const config = await initSupabase();
      setAuthEnabled(config.enabled);
      setPlans(config.plans ?? []);
      setGuestMaxWords(config.guest?.max_words_per_request ?? 100);
      setSessionIdleMinutes(config.session_idle_minutes ?? 30);
      const sb = getSupabase();
      if (!config.enabled || !sb) {
        setReady(true);
        return;
      }
      const { data } = await sb.auth.getSession();
      setSession(data.session);
      const { data: listener } = sb.auth.onAuthStateChange((_event, next) => {
        setSession(next);
        if (next) setIdleSignedOut(false);
      });
      unsub = () => listener.subscription.unsubscribe();
      setReady(true);
    })();
    return () => unsub?.();
  }, []);

  useEffect(() => {
    void refreshAccount();
  }, [refreshAccount]);

  const value = useMemo<AuthContextValue>(
    () => ({
      ready,
      authEnabled,
      session,
      user: session?.user ?? null,
      account,
      plans: plans.length ? plans : getCachedAuthConfig()?.plans ?? [],
      guestMaxWords,
      sessionIdleMinutes,
      idleSignedOut,
      clearIdleNotice: () => setIdleSignedOut(false),
      refreshAccount,
      setAccount,
      async signInWithPassword(email, password) {
        const sb = getSupabase();
        if (!sb) throw new Error("Auth is not configured.");
        const { error } = await sb.auth.signInWithPassword({ email, password });
        if (error) throw error;
        setIdleSignedOut(false);
      },
      async signUp(email, password) {
        const sb = getSupabase();
        if (!sb) throw new Error("Auth is not configured.");
        const { data, error } = await sb.auth.signUp({ email, password });
        if (error) throw error;
        setIdleSignedOut(false);
        if (data.session) return "signed_in";
        return "check_email";
      },
      async signInWithGoogle() {
        const sb = getSupabase();
        if (!sb) throw new Error("Auth is not configured.");
        const { error } = await sb.auth.signInWithOAuth({
          provider: "google",
          options: { redirectTo: window.location.origin },
        });
        if (error) throw error;
      },
      async signOut() {
        setIdleSignedOut(false);
        await signOut();
        await refreshAccount();
      },
    }),
    [
      ready,
      authEnabled,
      session,
      account,
      plans,
      guestMaxWords,
      sessionIdleMinutes,
      idleSignedOut,
      refreshAccount,
      signOut,
    ],
  );

  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 within AuthProvider");
  return ctx;
}
