import React, { createContext, useContext, useMemo, useCallback, useEffect, useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';

import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';

import { logoutEventEmitter } from '@/events/LogoutEventEmitter';
import { vaultStateEvents } from '@/events/VaultStateEvents';

type AppContextType = {
  isLoggedIn: boolean;
  isInitialized: boolean;
  username: string | null;
  logout: (errorMessage?: string) => Promise<void>;
  initializeAuth: () => Promise<boolean>;
  setAuthTokens: (username: string, accessToken: string, refreshToken: string) => Promise<void>;
  globalMessage: string | null;
  clearGlobalMessage: () => void;
}

const AppContext = createContext<AppContextType | undefined>(undefined);

/**
 * AppProvider that coordinates between auth, db, and webApi contexts.
 */
export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const auth = useAuth();
  const webApi = useWebApi();
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const isLoggingOutRef = useRef(false);
  const { t } = useTranslation();

  /**
   * Logout the user by revoking tokens and clearing the auth tokens from storage.
   * Prevents recursive logout calls by tracking logout state.
   *
   * NOTE: This is used for FORCED logout (from logoutEventEmitter). For user-initiated logout,
   * use the Settings page which checks isDirty and shows warning dialog.
   */
  const logout = useCallback(async (errorMessage?: string): Promise<void> => {
    if (isLoggingOutRef.current) {
      return;
    }

    try {
      isLoggingOutRef.current = true;
      await webApi.revokeTokens();
      // Use forced logout to preserve orphaned vault
      await auth.clearAuthForced(errorMessage);
    } catch (error) {
      console.error('Error during logout:', error);
    } finally {
      isLoggingOutRef.current = false;
      setIsLoggedIn(false);
    }
  }, [auth, webApi]);

  /**
   * Initialize the authentication state.
   *
   * @returns boolean indicating whether the user is logged in.
   */
  const initializeAuth = useCallback(async () : Promise<boolean> => {
    const isLoggedIn = await auth.initializeAuth();
    setIsLoggedIn(isLoggedIn);
    return isLoggedIn;
  }, [auth]);

  /**
   * Set auth tokens and update logged in state.
   */
  const setAuthTokens = useCallback(async (username: string, accessToken: string, refreshToken: string): Promise<void> => {
    await auth.setAuthTokens(username, accessToken, refreshToken);
    setIsLoggedIn(true);
  }, [auth]);

  /**
   * Subscribe to logout events from WebApiService.
   */
  useEffect(() => {
    const unsubscribe = logoutEventEmitter.subscribe(async (errorKey: string) => {
      await logout(t(errorKey));
    });

    return unsubscribe;
  }, [logout, t]);

  /**
   * Check for tokens in browser local storage on initial load when this context is mounted.
   */
  useEffect(() => {
    initializeAuth();
  }, [initializeAuth]);

  // Reflect logouts from other windows.
  useEffect(() => {
    return vaultStateEvents.onLoggedOut(() => {
      setIsLoggedIn(false);
    });
  }, []);

  const contextValue = useMemo(() => ({
    // Pass through auth state
    isInitialized: auth.isInitialized,
    username: auth.username,
    globalMessage: auth.globalMessage,
    // Wrap auth methods
    logout,
    initializeAuth,
    setAuthTokens,
    clearGlobalMessage: auth.clearGlobalMessage,
    isLoggedIn: isLoggedIn,
  }), [
    auth.isInitialized,
    auth.username,
    auth.globalMessage,
    auth.clearGlobalMessage,
    logout,
    initializeAuth,
    setAuthTokens,
    isLoggedIn,
  ]);

  return (
    <AppContext.Provider value={contextValue}>
      {children}
    </AppContext.Provider>
  );
};

/**
 * Hook to use the AppContext.
 */
export const useApp = (): AppContextType => {
  const context = useContext(AppContext);
  if (context === undefined) {
    throw new Error('useApp must be used within an AppProvider');
  }
  return context;
};