import React, { createContext, useContext, useState, ReactNode, useCallback, useRef, useEffect, useMemo } from 'react';
import { Platform, AppState, AppStateStatus } from 'react-native';
import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
import { ReactNativeHttpClient } from './react-native-http-client';
import { createReactNativeAuthProvider } from './react-native-auth-provider';
import { ReactNativeDPoPProvider } from './rn-dpop-provider';
import { UserDTO, AdminDTO } from '@explorins/pers-shared';
import { detectReactNativePlatform } from '../utils/platform-detection';

// Import manager types for TypeScript
import type {
  AuthManager,
  UserManager,
  TokenManager,
  BusinessManager,
  CampaignManager,
  RedemptionManager,
  TransactionManager,
  PurchaseManager,
  TenantManager,
  AnalyticsManager,
  DonationManager
} from '@explorins/pers-sdk/core';

// Re-export PersConfig for external use
export type { PersConfig } from '@explorins/pers-sdk/core';

/**
 * Context interface for PERS SDK React Native integration
 * 
 * ## Session Restoration Behavior
 * 
 * Session restoration is **asynchronous**. When the app starts:
 * 1. `isInitialized` becomes `true` when SDK is ready
 * 2. Session restoration happens automatically
 * 3. `isAuthenticated` and `user` update when restoration completes
 * 
 * **Always check `isInitialized` AND `isAuthenticated` before using `user`:**
 * 
 * ```tsx
 * const { user, isInitialized, isAuthenticated } = usePersSDK();
 * 
 * if (!isInitialized) return <LoadingSpinner />;
 * if (!isAuthenticated || !user) return <LoginScreen />;
 * return <WalletContent user={user} />;
 * ```
 * 
 * **Why `user` might be null on app restart:**
 * - Access tokens expire after 1 hour (auto-refreshed if app is active)
 * - Refresh tokens expire after 30 days (user must re-login)
 * - Session restoration is in progress
 * 
 * Listen for auth events to debug:
 * ```typescript
 * sdk.events.subscribe((event) => {
 *   console.log('[Auth Event]', event.type, event.details);
 * }, { domains: ['authentication'], replay: true });
 * ```
 * 
 * | Event | Meaning |
 * |-------|---------|
 * | `session_restored` | Tokens valid, user logged in |
 * | `session_restoration_failed` | Tokens expired, needs re-login |
 * 
 * @property sdk - Main SDK instance (null until initialized)
 * @property authProvider - Platform-specific auth provider
 * @property isInitialized - Whether SDK has been initialized (not whether user is authenticated!)
 * @property isAuthenticated - Whether user is currently authenticated
 * @property user - Current user data (null if not authenticated OR restoration in progress)
 */
export interface PersSDKContext {
  // Main SDK instance
  sdk: PersSDK | null;
  
  // Platform-specific providers
  authProvider: DefaultAuthProvider | null;
  
  // State
  isInitialized: boolean;
  isAuthenticated: boolean;
  user: UserDTO | AdminDTO | null;
  
  // Methods
  initialize: (config: PersConfig) => Promise<void>;
  setAuthenticationState: (user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => void;
  refreshUserData: () => Promise<void>;
  restoreSession: () => Promise<UserDTO | null>;
}

// Create the context
const SDKContext = createContext<PersSDKContext | null>(null);

/**
 * PERS SDK Provider for React Native
 * 
 * Wraps your app to provide SDK context to all child components.
 * Handles platform-specific initialization (DPoP, storage, etc.).
 * 
 * @param config - SDK configuration (see PersConfig)
 * @param config.apiProjectKey - Your PERS project key (required)
 * @param config.environment - 'staging' | 'production' (default: 'staging')
 * @param config.captureWalletEvents - Enable real-time blockchain events (default: true)
 * @param config.dpop - DPoP configuration for enhanced security
 * 
 * @example Basic usage
 * ```tsx
 * <PersSDKProvider config={{ apiProjectKey: 'my-project' }}>
 *   <App />
 * </PersSDKProvider>
 * ```
 * 
 * @example Disable wallet events
 * ```tsx
 * <PersSDKProvider config={{ 
 *   apiProjectKey: 'my-project',
 *   captureWalletEvents: false  // Disable auto-connect to blockchain events
 * }}>
 *   <App />
 * </PersSDKProvider>
 * ```
 */
export const PersSDKProvider: React.FC<{
  children: ReactNode;
  config?: PersConfig;
}> = ({ children, config }) => {
  const initializingRef = useRef(false);
  const eventUnsubscribeRef = useRef<(() => void) | null>(null);
  
  // State refs for stable functions to read current values
  const sdkRef = useRef<PersSDK | null>(null);
  const isInitializedRef = useRef(false);
  const isAuthenticatedRef = useRef(false);
  
  const [sdk, setSdk] = useState<PersSDK | null>(null);
  const [authProvider, setAuthProvider] = useState<DefaultAuthProvider | null>(null);
  
  const [isInitialized, setIsInitialized] = useState(false);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [user, setUser] = useState<UserDTO | AdminDTO | null>(null);

  // Keep state refs in sync immediately (not in useEffect to avoid race conditions)
  sdkRef.current = sdk;
  isInitializedRef.current = isInitialized;
  isAuthenticatedRef.current = isAuthenticated;

  const initialize = useCallback(async (config: PersConfig) => {
    // Prevent multiple initializations
    if (isInitialized || initializingRef.current) {
      return;
    }
    
    initializingRef.current = true;
    
    try {
      // Create HTTP client
      const httpClient = new ReactNativeHttpClient();
      
      const dpopConfig = config.dpop ?? {};
      
      const isDpopEnabled = dpopConfig.enabled ?? true;
      
      // Prepare DPoP settings for Auth Provider
      const dpopSettings = {
        enabled: isDpopEnabled,
        cryptoProvider: dpopConfig.cryptoProvider
      };

      // Inject Native DPoP Provider (crypto-based) for mobile platforms if not already provided
      if (Platform.OS !== 'web' && isDpopEnabled && !dpopSettings.cryptoProvider) {
        dpopSettings.cryptoProvider = new ReactNativeDPoPProvider();
      }

      // Create platform-aware auth provider if not provided
      let authProvider: DefaultAuthProvider;
      if (config.authProvider) {
        authProvider = config.authProvider as DefaultAuthProvider;
      } else {
        // Use our platform-aware auth provider that automatically selects LocalStorage (web) or AsyncStorage (mobile)
        authProvider = createReactNativeAuthProvider(config.apiProjectKey || 'default-project', {
          debug: false,
          dpop: dpopSettings
        });
      }
      
      // Enhanced config with platform-appropriate auth provider
      const sdkConfig: PersConfig = {
        ...config,
        authProvider,
        dpop: dpopSettings
      };
      
      // Initialize PersSDK with platform-aware auth provider
      const sdkInstance = new PersSDK(httpClient, sdkConfig);
      
      // Auto-configure data source and platform for React Native
      // Native apps MUST set channel as backend cannot auto-detect without browser User-Agent
      if (Platform.OS !== 'web') {
        sdkInstance.setDataSource({
          channel: 'mobile',
          source: config.dataSource?.source || 'app',
          medium: config.dataSource?.medium,
          campaign: config.dataSource?.campaign,
        });
        
        // Auto-detect platform info asynchronously
        detectReactNativePlatform().then(platformInfo => {
          sdkInstance.setPlatform(platformInfo);
        }).catch(error => {
          console.debug('[PersSDK] Platform detection failed:', error);
        });
      }
      
      // Subscribe to auth events BEFORE exposing SDK to consumers
      // This ensures no events are missed due to React timing
      eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
        if (event.type === 'session_restored' || event.type === 'login_success') {
          const details = event.details as { user?: UserDTO | AdminDTO };
          if (details?.user) {
            setUser(details.user);
            setIsAuthenticated(true);
          }
        }

        if (event.type === 'logout_success' || event.type === 'session_restoration_failed') {
          setUser(null);
          setIsAuthenticated(false);
        }
      }, { domains: ['authentication'], replay: true });
      
      setAuthProvider(authProvider);
      setSdk(sdkInstance);
      setIsInitialized(true);
    } catch (error) {
      console.error('Failed to initialize PERS SDK:', error);
      initializingRef.current = false;
      throw error;
    } finally {
      initializingRef.current = false;
    }
  }, [isInitialized]);
  
  // Cleanup subscription on unmount
  useEffect(() => {
    return () => {
      if (eventUnsubscribeRef.current) {
        eventUnsubscribeRef.current();
        eventUnsubscribeRef.current = null;
      }
    };
  }, []);

  const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
    setUser(user);
    setIsAuthenticated(isAuthenticated);
  }, []);

  // Auto-initialize if config is provided
  useEffect(() => {
    if (config && !isInitialized && !initializingRef.current) {
      initialize(config).catch(err => {
        console.error('Auto-initialization failed:', err);
      });
    }
  }, [config, isInitialized, initialize]);

  const refreshUserData = useCallback(async (): Promise<void> => {
    // Read from refs to get current values
    const currentSdk = sdkRef.current;
    const currentIsInitialized = isInitializedRef.current;

    if (!currentSdk || !currentIsInitialized) {
      throw new Error('SDK not initialized. Cannot refresh user data.');
    }
    
    try {
      const freshUserData = await currentSdk.users.getCurrentUser();
      setUser(freshUserData);
    } catch (error) {
      console.error('Failed to refresh user data:', error);
      throw error;
    }
  }, []); // No dependencies - reads from refs

  const restoreSession = useCallback(async (): Promise<UserDTO | null> => {
    // Read from refs to get current values
    const currentSdk = sdkRef.current;
    const currentIsInitialized = isInitializedRef.current;

    if (!currentSdk || !currentIsInitialized) {
      throw new Error('SDK not initialized. Call initialize() first.');
    }
    
    try {
      const userData = await currentSdk.restoreSession();
      if (userData) {
        setAuthenticationState(userData, true);
      }
      return userData;
    } catch (error) {
      console.error('[PersSDK] Failed to restore session:', error);
      throw error;
    }
  }, [setAuthenticationState]);

  // iOS/Android: Monitor app state and validate tokens when app becomes active
  useEffect(() => {
    if (!sdk || Platform.OS === 'web') {
      return;
    }

    const handleAppStateChange = async (nextAppState: AppStateStatus) => {
      if (nextAppState === 'active' && isAuthenticated) {
        try {
          // Trigger token validation when app resumes from background
          // This ensures tokens are checked after extended inactivity
          await sdk.auth.ensureValidToken();
        } catch (error) {
          console.warn('[PersSDK] Token validation failed on app resume:', error);
        }
      }
    };

    const subscription = AppState.addEventListener('change', handleAppStateChange);

    return () => {
      subscription?.remove();
    };
  }, [sdk, isAuthenticated]);

  const contextValue: PersSDKContext = useMemo(() => ({
    // Main SDK instance
    sdk,
    
    // Platform-specific providers
    authProvider,
    
    // State
    isInitialized,
    isAuthenticated,
    user,
    
    // Methods - expose functions directly, not through refs
    initialize,
    setAuthenticationState,
    refreshUserData,
    restoreSession,
  }), [
    sdk,
    authProvider,
    isInitialized,
    isAuthenticated,
    user,
    initialize,
    setAuthenticationState,
    refreshUserData,
    restoreSession
  ]);

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

// Custom hook to use the SDK context
export const usePersSDK = (): PersSDKContext => {
  const context = useContext(SDKContext);
  
  if (!context) {
    throw new Error('usePersSDK must be used within a PersSDKProvider');
  }
  
  return context;
};