import {
  type DeviceRecord,
  getOwnProfile,
  signOut as supabaseSignOut,
  type Profile,
} from '@chat-app/shared/auth';
import { changeLocale, isSupportedLocale } from '@chat-app/shared/i18n';
import type { Session } from '@supabase/supabase-js';
import {
  createContext,
  type ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from 'react';
import { useTranslation } from 'react-i18next';

import { findExistingDevice } from '../lib/device';
import { setSecretStoreUser } from '../lib/secretStore';
import { supabase } from '../lib/supabase';

interface AuthContextValue {
  session: Session | null;
  profile: Profile | null;
  device: DeviceRecord | null;
  // null while we're still resolving the very first auth state.
  ready: boolean;
  // null until a device lookup has finished for the current session.
  deviceLookupDone: boolean;
  refreshProfile: () => Promise<void>;
  refreshDevice: () => Promise<void>;
  setDevice: (device: DeviceRecord | null) => void;
  signOut: () => Promise<void>;
}

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

export function AuthProvider({ children }: { children: ReactNode }) {
  const { i18n } = useTranslation();
  const [session, setSession] = useState<Session | null>(null);
  const [ready, setReady] = useState(false);
  const [profile, setProfile] = useState<Profile | null>(null);
  const [device, setDevice] = useState<DeviceRecord | null>(null);
  const [deviceLookupDone, setDeviceLookupDone] = useState(false);

  // Initial session + auth subscription. We verify the cached JWT against the
  // server (via getUser) once on mount. Only purge the session on an
  // unambiguous 401/403 — a network failure (Supabase stack offline) must not
  // log the user out, otherwise every local `supabase stop` wipes their session.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      const { data: sessionRes } = await supabase.auth.getSession();
      if (cancelled) return;
      if (sessionRes.session) {
        const { error } = await supabase.auth.getUser();
        if (cancelled) return;
        if (error) {
          const status = (error as { status?: number }).status;
          if (status === 401 || status === 403) {
            // Token genuinely invalid — wipe.
            await supabase.auth.signOut({ scope: 'local' }).catch(() => {
              /* ignore */
            });
            setSession(null);
          } else {
            // Network / server unreachable — keep cached session, let reads
            // fail gracefully and recover when the stack is back.
            console.warn('auth.getUser failed, keeping cached session:', error);
            setSession(sessionRes.session);
          }
        } else {
          setSession(sessionRes.session);
        }
      } else {
        setSession(null);
      }
      setReady(true);
    })();
    const { data: sub } = supabase.auth.onAuthStateChange((_event, s) => {
      setSession(s);
      setReady(true);
      void setSecretStoreUser(s?.user.id ?? null);
    });
    return () => {
      cancelled = true;
      sub.subscription.unsubscribe();
    };
  }, []);

  const refreshProfile = useCallback(async () => {
    if (!session) {
      setProfile(null);
      return;
    }
    const p = await getOwnProfile(supabase);
    setProfile(p);
    if (p && p.locale !== i18n.resolvedLanguage && isSupportedLocale(p.locale)) {
      void changeLocale(p.locale);
    }
  }, [session, i18n.resolvedLanguage]);

  const refreshDevice = useCallback(async () => {
    if (!session) {
      setDevice(null);
      setDeviceLookupDone(false);
      return;
    }
    setDeviceLookupDone(false);
    const found = await findExistingDevice(session.user.id);
    setDevice(found);
    setDeviceLookupDone(true);
  }, [session]);

  // Re-pull profile + device whenever session flips.
  useEffect(() => {
    if (!session) {
      setProfile(null);
      setDevice(null);
      setDeviceLookupDone(false);
      return;
    }
    void refreshProfile().catch((err: unknown) => {
      console.error('refreshProfile failed', err);
    });
    void refreshDevice().catch((err: unknown) => {
      console.error('refreshDevice failed', err);
      setDeviceLookupDone(true);
    });
  }, [session, refreshProfile, refreshDevice]);

  const signOut = useCallback(async () => {
    await supabaseSignOut(supabase);
  }, []);

  const value = useMemo<AuthContextValue>(
    () => ({
      session,
      profile,
      device,
      ready,
      deviceLookupDone,
      refreshProfile,
      refreshDevice,
      setDevice,
      signOut,
    }),
    [session, profile, device, ready, deviceLookupDone, refreshProfile, refreshDevice, 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 inside <AuthProvider>');
  return ctx;
}
