import * as SecureStore from 'expo-secure-store';
import { create } from 'zustand';
import {
  signInWithCustomToken,
  signOut as firebaseSignOut,
} from 'firebase/auth';
import { apiClient } from '@/api/client';
import type { Human } from '@/api/types';
import { firebaseAuth } from '@/firebase';
import { logError, ApiError } from '@/lib/errors';
import {
  startPushTokenSync,
  stopPushTokenSync,
  syncPushToken,
  unregisterPushToken,
} from '@/lib/push-notifications';

const AUTH_TOKEN_KEY = 'auth_token';

async function readPersistedToken(): Promise<string | null> {
  try {
    return await SecureStore.getItemAsync(AUTH_TOKEN_KEY);
  } catch {
    // SecureStore failures are non-fatal — proceed unauthenticated.
    return null;
  }
}

async function persistToken(token: string): Promise<void> {
  await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token);
}

async function clearPersistedToken(): Promise<void> {
  try {
    await SecureStore.deleteItemAsync(AUTH_TOKEN_KEY);
  } catch {
    // ignore — in-memory clear still happens via the caller
  }
}

async function signInToFirebase(): Promise<void> {
  try {
    const { token } = await apiClient.getFirebaseToken();
    await signInWithCustomToken(firebaseAuth, token);
  } catch (err) {
    // Firestore subscriptions will fail until the next successful sign-in; the
    // rest of the app keeps working against Orion. Sentry catches the failure.
    logError(err, { scope: 'auth.firebase' });
  }
}

type AuthStatus = 'idle' | 'restoring' | 'unauthenticated' | 'authenticated';

interface AuthState {
  status: AuthStatus;
  user: Human | null;
  token: string | null;
  isRequestingCode: boolean;
  isSigningIn: boolean;
  isSigningOut: boolean;
  error: string | null;
  restoreSession: () => Promise<void>;
  requestCode: (email: string) => Promise<void>;
  signIn: (email: string, code: string) => Promise<void>;
  signOut: () => Promise<void>;
  /** Re-fetch the current user from Orion (e.g. after an avatar change). */
  refreshUser: () => Promise<void>;
  /**
   * Wipes the session in response to a server-detected auth failure (e.g. a
   * 401 surfaced through react-query). Does not call `/auth/sign-out`; the
   * server already considers us unauthenticated.
   */
  invalidateSession: () => Promise<void>;
  clearError: () => void;
}

export const useAuthStore = create<AuthState>((set, get) => ({
  status: 'idle',
  user: null,
  token: null,
  isRequestingCode: false,
  isSigningIn: false,
  isSigningOut: false,
  error: null,

  restoreSession: async () => {
    set({ status: 'restoring' });

    const token = await readPersistedToken();
    if (!token) {
      set({ status: 'unauthenticated' });
      return;
    }

    apiClient.setToken(token);
    set({ token });

    try {
      const user = await apiClient.me();
      await signInToFirebase();
      set({ status: 'authenticated', user });
      startPushTokenSync();
      void syncPushToken();
    } catch (err) {
      // Expected on expired/invalid tokens — fall back to the login screen.
      logError(err, { scope: 'auth.restore' });
      await get().invalidateSession();
    }
  },

  requestCode: async (email: string) => {
    set({ isRequestingCode: true, error: null });
    try {
      await apiClient.requestCode({ email });
    } catch (e) {
      const message = e instanceof ApiError ? e.message : 'Failed to send code';
      set({ error: message });
      throw e;
    } finally {
      set({ isRequestingCode: false });
    }
  },

  signIn: async (email: string, code: string) => {
    set({ isSigningIn: true, error: null });
    try {
      const { human, token } = await apiClient.signIn({ email, code });
      await persistToken(token);
      apiClient.setToken(token);
      set({ token });
      await signInToFirebase();
      set({ status: 'authenticated', user: human });
      startPushTokenSync();
      void syncPushToken();
    } catch (e) {
      const message = e instanceof ApiError ? e.message : 'Failed to sign in';
      set({ error: message });
      throw e;
    } finally {
      set({ isSigningIn: false });
    }
  },

  signOut: async () => {
    set({ isSigningOut: true });
    stopPushTokenSync();
    // Unregister the push token first — once the bearer token is cleared the
    // backend call would 401. Best-effort: failures must not block sign-out.
    await unregisterPushToken();
    try {
      await apiClient.signOut();
    } catch (err) {
      // Best-effort — sign out locally regardless.
      logError(err, { scope: 'auth.signOut' });
    } finally {
      await firebaseSignOut(firebaseAuth).catch((err) =>
        logError(err, { scope: 'auth.firebaseSignOut' }),
      );
      apiClient.setToken(null);
      await clearPersistedToken();
      set({
        status: 'unauthenticated',
        user: null,
        token: null,
        isSigningOut: false,
        error: null,
      });
    }
  },

  refreshUser: async () => {
    try {
      const user = await apiClient.me();
      set({ user });
    } catch (err) {
      logError(err, { scope: 'auth.refreshUser' });
    }
  },

  invalidateSession: async () => {
    stopPushTokenSync();
    apiClient.setToken(null);
    await clearPersistedToken();
    set({ status: 'unauthenticated', user: null, token: null });

    await unregisterPushToken(); // Best-effort
  },

  clearError: () => set({ error: null }),
}));
