/**
 * @ai-summary Auth0 provider wrapper with API token injection
 */

import React from 'react';
import { Auth0Provider as BaseAuth0Provider, useAuth0 } from '@auth0/auth0-react';
import { useNavigate } from 'react-router-dom';
import { apiClient, setAuthReady } from '../api/client';
import { createIndexedDBAdapter } from '../utils/indexeddb-storage';
import { setAuthInitialized } from './auth-gate';
import logger from '../../utils/logger';

interface Auth0ProviderProps {
  children: React.ReactNode;
}

export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
  const navigate = useNavigate();

  const domain = import.meta.env.VITE_AUTH0_DOMAIN;
  const clientId = import.meta.env.VITE_AUTH0_CLIENT_ID;
  const audience = import.meta.env.VITE_AUTH0_AUDIENCE;

  const onRedirectCallback = (appState?: { returnTo?: string }) => {
    logger.debug('Auth0 redirect callback triggered', { returnTo: appState?.returnTo });
    // Route to callback page which will check user status and redirect appropriately
    // Pass the intended destination as state for after status check
    navigate('/callback', {
      replace: true,
      state: { returnTo: appState?.returnTo || '/garage' },
    });
  };

  return (
    <BaseAuth0Provider
      domain={domain}
      clientId={clientId}
      authorizationParams={{
        // Dynamic callback - supports staging (staging.motovaultpro.com) and production (motovaultpro.com)
        redirect_uri: `${window.location.origin}/callback`,
        audience: audience,
        scope: 'openid profile email offline_access',
      }}
      onRedirectCallback={onRedirectCallback}
      // Mobile-optimized: use IndexedDB for better mobile compatibility
      cache={createIndexedDBAdapter()}
      useRefreshTokens={true}
      useRefreshTokensFallback={true}
    >
      <TokenInjector>{children}</TokenInjector>
    </BaseAuth0Provider>
  );
};

// Component to inject token into API client with mobile support
const TokenInjector: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { getAccessTokenSilently, isAuthenticated, isLoading, logout } = useAuth0();
  const [retryCount, setRetryCount] = React.useState(0);
  const validatingRef = React.useRef(false);

  // Helper function to get token with enhanced retry logic for mobile devices
  const getTokenWithRetry = async (maxRetries = 5, delayMs = 300): Promise<any> => {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        // Enhanced progressive strategy for mobile compatibility
        let tokenOptions: any;
        if (attempt === 0) {
          // First attempt: try cache with shorter timeout
          tokenOptions = { timeoutInSeconds: 10, cacheMode: 'on' as const };
        } else if (attempt === 1) {
          // Second attempt: cache with longer timeout
          tokenOptions = { timeoutInSeconds: 20, cacheMode: 'on' as const };
        } else if (attempt === 2) {
          // Third attempt: force refresh with reasonable timeout
          tokenOptions = { timeoutInSeconds: 15, cacheMode: 'off' as const };
        } else if (attempt === 3) {
          // Fourth attempt: force refresh with longer timeout
          tokenOptions = { timeoutInSeconds: 30, cacheMode: 'off' as const };
        } else {
          // Final attempt: default behavior with maximum timeout
          tokenOptions = { timeoutInSeconds: 45 };
        }

        const token = await getAccessTokenSilently(tokenOptions);
        logger.debug(`Token acquired on attempt ${attempt + 1}`);
        return token;
      } catch (error: any) {
        logger.warn(`Token attempt ${attempt + 1}/${maxRetries} failed`, {
          error: error.message || String(error),
        });

        // Mobile-specific: longer delays and more attempts
        if (attempt < maxRetries - 1) {
          const delay = delayMs * Math.pow(1.5, attempt);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    logger.error('All token acquisition attempts failed');
    return null;
  };

  // Prevent stale session state when cached token is no longer valid
  React.useEffect(() => {
    if (!isAuthenticated || isLoading || validatingRef.current) return;
    if (window.location.pathname === '/callback') return;

    const validateToken = async () => {
      validatingRef.current = true;
      try {
        await getAccessTokenSilently({ cacheMode: 'off', timeoutInSeconds: 10 });
      } catch (error: any) {
        const errorType = error?.error || error?.message || '';
        if (errorType.includes('login_required') || errorType.includes('consent_required') ||
            errorType.includes('invalid_grant')) {
          logger.warn('Stale token detected, clearing auth state');
          const { indexedDBStorage } = await import('../utils/indexeddb-storage');
          await indexedDBStorage.clearAll();
          logout({ openUrl: false });
        }
      } finally {
        validatingRef.current = false;
      }
    };

    validateToken();
  }, [isAuthenticated, isLoading, getAccessTokenSilently, logout]);

  React.useEffect(() => {
    let interceptorId: number | undefined;

    if (isAuthenticated) {
      // Enhanced pre-warm token cache for mobile devices with IndexedDB wait
      const initializeToken = async () => {
        // Wait for IndexedDB to be ready first
        try {
          const { indexedDBStorage } = await import('../utils/indexeddb-storage');
          await indexedDBStorage.waitForReady();
          logger.debug('IndexedDB storage is ready');
        } catch (error) {
          logger.warn('IndexedDB not ready, proceeding anyway', { error: String(error) });
        }

        // Minimal delay only for mobile devices (desktop needs no delay since IndexedDB is already ready)
        const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
        if (isMobile) {
          await new Promise(resolve => setTimeout(resolve, 50));
        }

        try {
          const token = await getTokenWithRetry();
          if (token) {
            logger.debug('Token pre-warming successful');
            setRetryCount(0);
            setAuthReady(true);
            setAuthInitialized(true); // Signal that auth is fully ready
          } else {
            logger.error('Failed to acquire token after retries');
            setRetryCount(prev => prev + 1);
          }
        } catch (error) {
          logger.error('Token initialization failed', { error: String(error) });
          setRetryCount(prev => prev + 1);
        }
      };

      initializeToken();

      // Add token to all API requests with enhanced error handling and IndexedDB wait
      interceptorId = apiClient.interceptors.request.use(async (config) => {
        try {
          // Ensure IndexedDB is ready before getting tokens
          const { indexedDBStorage } = await import('../utils/indexeddb-storage');
          await indexedDBStorage.waitForReady();

          const token = await getTokenWithRetry();
          if (token) {
            config.headers.Authorization = `Bearer ${token}`;
            setAuthReady(true);
          } else {
            logger.error('No token available for request', { url: config.url });
            // Allow request to proceed - backend will return 401 if needed
          }
        } catch (error: any) {
          logger.error('Failed to get access token for request', { error: error.message || String(error) });
          // Allow request to proceed - backend will return 401 if needed
        }
        return config;
      });
    } else {
      setRetryCount(0);
      setAuthReady(false);
      setAuthInitialized(false); // Reset auth gate when not authenticated
    }

    // Cleanup function to remove interceptor
    return () => {
      if (interceptorId !== undefined) {
        apiClient.interceptors.request.eject(interceptorId);
      }
    };
  }, [isAuthenticated, getAccessTokenSilently, retryCount]);

  return <>{children}</>;
};
