/**
 * Authentication Context
 * Provides unified authentication interface that works with both:
 * - Backend token exchange (localhost/development)
 * - Auth0 Provider (production)
 */

import { createContext, useContext, useEffect, useState, ReactNode, useRef } from 'react';
import { Auth0Provider, useAuth0 as useAuth0Hook } from '@auth0/auth0-react';
import { TokenManager, isTokenExpired } from '../utils/tokenManager';
import { exchangeCodeForTokens, refreshAccessToken, getCurrentUser, logout as logoutApi } from '../services/authApi';

interface User {
  userId?: string;
  employeeId?: string;
  email?: string;
  firstName?: string;
  lastName?: string;
  displayName?: string;
  department?: string;
  designation?: string;
  role?: 'USER' | 'MANAGEMENT' | 'ADMIN';
  sub?: string;
  name?: string;
  picture?: string;
  nickname?: string;
}

interface AuthContextType {
  isAuthenticated: boolean;
  isLoading: boolean;
  user: User | null;
  error: Error | null;
  login: () => Promise<void>;
  logout: () => Promise<void>;
  getAccessTokenSilently: () => Promise<string | null>;
  refreshTokenSilently: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

interface AuthProviderProps {
  children: ReactNode;
}

/**
 * Check if running on localhost
 * Note: Function reserved for future use
 * @internal - Reserved for future use
 */
export const _isLocalhost = (): boolean => {
  return (
    window.location.hostname === 'localhost' ||
    window.location.hostname === '127.0.0.1' ||
    window.location.hostname === ''
  );
};

/**
 * Backend-based Auth Provider (for localhost)
 */
function BackendAuthProvider({ children }: { children: ReactNode }) {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [user, setUser] = useState<User | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [isLoggingOut, setIsLoggingOut] = useState(false);

  // Check authentication status on mount
  // Only check if we're not in the middle of a logout process
  useEffect(() => {
    // PRIORITY 1: Check for logout flags in sessionStorage (survives page reload during logout)
    const logoutFlag = sessionStorage.getItem('__logout_in_progress__');
    const forceLogout = sessionStorage.getItem('__force_logout__');
    
    if (logoutFlag === 'true' || forceLogout === 'true') {
      // Remove flags
      sessionStorage.removeItem('__logout_in_progress__');
      sessionStorage.removeItem('__force_logout__');
      
      // Clear all tokens one more time (aggressive)
      TokenManager.clearAll();
      
      // Also manually clear everything
      try {
        localStorage.clear();
        sessionStorage.clear();
      } catch (e) {
        console.error('Error clearing storage:', e);
      }
      
      // Set unauthenticated state
      setIsAuthenticated(false);
      setUser(null);
      setIsLoading(false);
      setError(null);
      
      return;
    }
    
    // PRIORITY 2: Check if URL has logout parameter (from redirect)
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('logout') || urlParams.has('okta_logged_out')) {
      TokenManager.clearAll();
      localStorage.clear();
      sessionStorage.clear();
      setIsAuthenticated(false);
      setUser(null);
      setIsLoading(false);
      // Clean URL but preserve okta_logged_out flag if it exists (for prompt=login)
      const cleanParams = new URLSearchParams();
      if (urlParams.has('okta_logged_out')) {
        cleanParams.set('okta_logged_out', 'true');
      }
      const newUrl = cleanParams.toString() ? `/?${cleanParams.toString()}` : '/';
      window.history.replaceState({}, document.title, newUrl);
      return;
    }
    
    // PRIORITY 3: Skip auth check if on callback page - let callback handler process first
    // This is critical for production mode where we need to exchange code for tokens
    // before we can verify session with server
    if (window.location.pathname === '/login/callback') {
      // Don't check auth status here - let the callback handler do its job
      // The callback handler will set isAuthenticated after successful token exchange
      return;
    }
    
    // PRIORITY 4: Check authentication status
    const token = TokenManager.getAccessToken();
    const refreshToken = TokenManager.getRefreshToken();
    const userData = TokenManager.getUserData();
    const hasAuthData = token || refreshToken || userData;
    
    // Check if we're in production mode (tokens in httpOnly cookies, not accessible to JS)
    const isProductionMode = import.meta.env.PROD || import.meta.env.MODE === 'production';
    
    // In production: Always verify with server (cookies are sent automatically)
    // In development: Check local auth data first
    if (isProductionMode) {
      // Production: Verify session with server via httpOnly cookie
      if (!isLoggingOut) {
        checkAuthStatus();
      } else {
        setIsLoading(false);
      }
    } else {
      // Development: If no auth data exists, user is not authenticated
      if (!hasAuthData) {
        setIsAuthenticated(false);
        setUser(null);
        setIsLoading(false);
        return;
      }
      
      // PRIORITY 5: Only check auth status if we have some auth data AND we're not logging out
      if (!isLoggingOut) {
        checkAuthStatus();
      } else {
        setIsLoading(false);
      }
    }
  }, [isLoggingOut]);

  // Silent refresh interval
  useEffect(() => {
    if (!isAuthenticated) return;

    const isProductionMode = import.meta.env.PROD || import.meta.env.MODE === 'production';

    const checkAndRefresh = async () => {
      if (isProductionMode) {
        // In production, proactively refresh the session every 10 minutes
        // The httpOnly cookie will be sent automatically
        try {
          await refreshTokenSilently();
        } catch (error) {
          console.error('Silent refresh failed:', error);
        }
      } else {
        // In development, check token expiration
        const token = TokenManager.getAccessToken();
        if (token && isTokenExpired(token, 5)) {
          // Token expires in less than 5 minutes, refresh it
          try {
            await refreshTokenSilently();
          } catch (error) {
            console.error('Silent refresh failed:', error);
          }
        }
      }
    };

    // Check every 10 minutes in production, 5 minutes in development
    const intervalMs = isProductionMode ? 10 * 60 * 1000 : 5 * 60 * 1000;
    const interval = setInterval(checkAndRefresh, intervalMs);
    return () => clearInterval(interval);
  }, [isAuthenticated]);

  // Handle callback from OAuth redirect
  // Use ref to prevent duplicate calls (React StrictMode runs effects twice in dev)
  const callbackProcessedRef = useRef(false);
  
  useEffect(() => {
    // Skip if already processed or not on callback page
    if (callbackProcessedRef.current || window.location.pathname !== '/login/callback') {
      return;
    }

    const handleCallback = async () => {
      // Mark as processed immediately to prevent duplicate calls
      callbackProcessedRef.current = true;
      
      const urlParams = new URLSearchParams(window.location.search);
      const code = urlParams.get('code');
      const errorParam = urlParams.get('error');

      // Clean URL immediately to prevent re-running on re-renders
      window.history.replaceState({}, document.title, '/login/callback');

      if (errorParam) {
        setError(new Error(`Authentication error: ${errorParam}`));
        setIsLoading(false);
        return;
      }

      if (!code) {
        setIsLoading(false);
        return;
      }

      try {
        setIsLoading(true);
        setIsAuthenticated(false);
        setError(null);
        
        // IMPORTANT: redirectUri must match the one used in initial Okta authorization request
        // This is the frontend callback URL, NOT the backend URL
        // Backend will use this same URI when exchanging code with Okta
        const redirectUri = `${window.location.origin}/login/callback`;
        
        const result = await exchangeCodeForTokens(code, redirectUri);
        
        setUser(result.user);
        setIsAuthenticated(true);
        setError(null);
        
        // Clean URL after success
        window.history.replaceState({}, document.title, '/');
      } catch (err: any) {
        console.error('❌ Token exchange error in AuthContext:', err);
        setError(err);
        setIsAuthenticated(false);
        setUser(null);
        // Reset ref on error so user can retry if needed
        callbackProcessedRef.current = false;
      } finally {
        setIsLoading(false);
      }
    };

    handleCallback();
  }, []); // Empty deps - only run once on mount

  const checkAuthStatus = async () => {
    // Don't check auth status if we're in the middle of logging out
    if (isLoggingOut) {
      setIsLoading(false);
      return;
    }
    
    const isProductionMode = import.meta.env.PROD || import.meta.env.MODE === 'production';
    
    try {
      setIsLoading(true);
      
      // PRODUCTION MODE: Verify session via httpOnly cookie
      // The cookie is sent automatically with the request (withCredentials: true)
      if (isProductionMode) {
        const storedUser = TokenManager.getUserData();
        
        // Try to get current user from server - this validates the httpOnly cookie
        try {
          const userData = await getCurrentUser();
          setUser(userData);
          TokenManager.setUserData(userData);
          setIsAuthenticated(true);
        } catch (error: any) {
          // If 401, try to refresh the token (refresh token is also in httpOnly cookie)
          if (error?.response?.status === 401) {
            try {
              await refreshTokenSilently();
              // Retry getting user after refresh
              const userData = await getCurrentUser();
              setUser(userData);
              TokenManager.setUserData(userData);
              setIsAuthenticated(true);
            } catch {
              // Refresh failed - clear user data and show login
              TokenManager.clearAll();
              setIsAuthenticated(false);
              setUser(null);
            }
          } else if (error?.isConnectionError) {
            // Backend not reachable - use stored user data if available
            if (storedUser) {
              setUser(storedUser);
              setIsAuthenticated(true);
            } else {
              setIsAuthenticated(false);
              setUser(null);
            }
          } else {
            // Other error - clear and show login
            TokenManager.clearAll();
            setIsAuthenticated(false);
            setUser(null);
          }
        }
        return;
      }
      
      // DEVELOPMENT MODE: Check local token
      const token = TokenManager.getAccessToken();
      const storedUser = TokenManager.getUserData();

      // If no token at all, user is not authenticated
      if (!token) {
        setIsAuthenticated(false);
        setUser(null);
        setIsLoading(false);
        return;
      }

      // Check if token is expired
      if (isTokenExpired(token)) {
        // Token expired, try to refresh
        try {
          await refreshTokenSilently();
          // Refresh succeeded, check again
          const newToken = TokenManager.getAccessToken();
          if (newToken && !isTokenExpired(newToken)) {
            const refreshedUser = TokenManager.getUserData();
            if (refreshedUser) {
              setUser(refreshedUser);
              setIsAuthenticated(true);
            } else {
              // Fetch user data
              try {
                const userData = await getCurrentUser();
                setUser(userData);
                TokenManager.setUserData(userData);
                setIsAuthenticated(true);
              } catch {
                // Token might be invalid, clear everything
                TokenManager.clearAll();
                setIsAuthenticated(false);
                setUser(null);
              }
            }
          } else {
            // Refresh failed, user is logged out
            TokenManager.clearAll();
            setIsAuthenticated(false);
            setUser(null);
          }
        } catch {
          // Refresh failed, clear everything
          TokenManager.clearAll();
          setIsAuthenticated(false);
          setUser(null);
        }
      } else {
        // Token is valid
        if (storedUser) {
          setUser(storedUser);
          setIsAuthenticated(true);
        } else {
          // Fetch user data to ensure token is still valid
          try {
            const userData = await getCurrentUser();
            setUser(userData);
            TokenManager.setUserData(userData);
            setIsAuthenticated(true);
          } catch {
            // Token might be invalid, clear everything
            TokenManager.clearAll();
            setIsAuthenticated(false);
            setUser(null);
          }
        }
      }
    } catch (err: any) {
      console.error('Error checking auth status:', err);
      setError(err);
      TokenManager.clearAll();
      setIsAuthenticated(false);
      setUser(null);
    } finally {
      setIsLoading(false);
    }
  };

  const login = async () => {
    try {
      setError(null);
      // Redirect to Okta login
      const oktaDomain = import.meta.env.VITE_OKTA_DOMAIN || 'https://dev-830839.oktapreview.com';
      const clientId = import.meta.env.VITE_OKTA_CLIENT_ID || '0oa2jgzvrpdwx2iqd0h8';
      const redirectUri = `${window.location.origin}/login/callback`;
      const responseType = 'code';
      const scope = 'openid profile email';
      const state = Math.random().toString(36).substring(7);

      // Check if we're coming from a logout - if so, add prompt=login to force re-authentication
      const urlParams = new URLSearchParams(window.location.search);
      const isAfterLogout = urlParams.has('logout') || urlParams.has('okta_logged_out');
      
      let authUrl = `${oktaDomain}/oauth2/default/v1/authorize?` +
        `client_id=${clientId}&` +
        `redirect_uri=${encodeURIComponent(redirectUri)}&` +
        `response_type=${responseType}&` +
        `scope=${encodeURIComponent(scope)}&` +
        `state=${state}`;
      
      // Add prompt=login if coming from logout to force re-authentication
      // This ensures Okta requires login even if a session still exists
      if (isAfterLogout) {
        authUrl += `&prompt=login`;
      }

      window.location.href = authUrl;
    } catch (err: any) {
      setError(err);
      throw err;
    }
  };

  const logout = async () => {
    try {
      // CRITICAL: Get id_token from TokenManager before clearing anything
      // Okta logout endpoint works better with id_token_hint to properly end the session
      // Note: Currently not used but kept for future Okta integration
      void TokenManager.getIdToken();
      
      // Set logout flag to prevent auto-authentication after redirect
      // This must be set BEFORE clearing storage so it survives
      sessionStorage.setItem('__logout_in_progress__', 'true');
      sessionStorage.setItem('__force_logout__', 'true');
      setIsLoggingOut(true);
      
      // Reset auth state FIRST to prevent any re-authentication
      setIsAuthenticated(false);
      setUser(null);
      setError(null);
      setIsLoading(true); // Set loading to prevent checkAuthStatus from running
      
      // Call backend logout API to clear server-side session and httpOnly cookies
      // IMPORTANT: This MUST be called before clearing local storage to clear httpOnly cookies
      try {
        await logoutApi();
      } catch (err) {
        console.error('🚪 Logout API error:', err);
        console.warn('🚪 Backend logout failed - httpOnly cookies may not be cleared');
        // Continue with logout even if API call fails
      }
      
      // Clear all authentication data EXCEPT the logout flags and id_token (we need it for Okta logout)
      
      // Clear tokens but preserve logout flags
      const logoutInProgress = sessionStorage.getItem('__logout_in_progress__');
      const forceLogout = sessionStorage.getItem('__force_logout__');
      
      // Use TokenManager.clearAll() but then restore logout flags
      TokenManager.clearAll();
      
      // Restore logout flags immediately after clearAll
      if (logoutInProgress) sessionStorage.setItem('__logout_in_progress__', logoutInProgress);
      if (forceLogout) sessionStorage.setItem('__force_logout__', forceLogout);
      
      // Small delay to ensure sessionStorage is written before redirect
      await new Promise(resolve => setTimeout(resolve, 100));
      
      // Redirect directly to login page with flags
      // The okta_logged_out flag will trigger prompt=login in the login() function
      // This forces re-authentication even if Okta session still exists
      const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
      window.location.replace(loginUrl);
    } catch (error) {
      console.error('🚪 Logout error:', error);
      // Force redirect even on error - clear everything and redirect to login
      try {
        localStorage.clear();
        sessionStorage.clear();
        sessionStorage.setItem('__logout_in_progress__', 'true');
        const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
        window.location.replace(loginUrl);
      } catch {
        // Last resort - redirect to home
        window.location.replace('/?logout=' + Date.now());
      }
    }
  };

  const getAccessTokenSilently = async (): Promise<string | null> => {
    const isProductionMode = import.meta.env.PROD || import.meta.env.MODE === 'production';
    
    // In production mode, tokens are in httpOnly cookies
    // We can't access them directly, but API calls will include them automatically
    if (isProductionMode) {
      // If user is authenticated, return a placeholder indicating cookies are used
      // The actual token is in httpOnly cookie and sent automatically with requests
      if (isAuthenticated) {
        return 'cookie-based-auth'; // Placeholder - actual auth via cookies
      }
      
      // Try to refresh the session
      try {
        await refreshTokenSilently();
        return isAuthenticated ? 'cookie-based-auth' : null;
      } catch {
        return null;
      }
    }
    
    // Development mode: tokens in localStorage
    const token = TokenManager.getAccessToken();
    if (token && !isTokenExpired(token)) {
      return token;
    }

    // Try to refresh
    try {
      await refreshTokenSilently();
      return TokenManager.getAccessToken();
    } catch {
      return null;
    }
  };

  const refreshTokenSilently = async (): Promise<void> => {
    const isProductionMode = import.meta.env.PROD || import.meta.env.MODE === 'production';
    
    try {
      const newToken = await refreshAccessToken();
      
      // In production, refresh might not return token (it's in httpOnly cookie)
      // but if the call succeeded, the session is valid
      if (isProductionMode) {
        // Session refreshed via cookies
        return;
      }
      
      if (newToken) {
        // Token refreshed successfully (development mode)
        return;
      }
      throw new Error('Failed to refresh token');
    } catch (err) {
      TokenManager.clearAll();
      setIsAuthenticated(false);
      setUser(null);
      throw err;
    }
  };

  const value: AuthContextType = {
    isAuthenticated,
    isLoading,
    user,
    error,
    login,
    logout,
    getAccessTokenSilently,
    refreshTokenSilently,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

/**
 * Auth0-based Auth Provider (for production)
 * Note: Reserved for future use when Auth0 integration is needed
 * @internal - Reserved for future use
 */
export function _Auth0AuthProvider({ children }: { children: ReactNode }) {
  return (
    <Auth0Provider
      domain="https://dev-830839.oktapreview.com/oauth2/default/v1"
      clientId="0oa2j8slwj5S4bG5k0h8"
      authorizationParams={{
        redirect_uri: window.location.origin + '/login/callback',
      }}
      onRedirectCallback={(_appState) => {
        // Auth0 redirect callback handled
      }}
    >
      <Auth0ContextWrapper>{children}</Auth0ContextWrapper>
    </Auth0Provider>
  );
}


/**
 * Wrapper to convert Auth0 hook to our context format
 */
function Auth0ContextWrapper({ children }: { children: ReactNode }) {
  const {
    isAuthenticated: auth0IsAuthenticated,
    isLoading: auth0IsLoading,
    user: auth0User,
    error: auth0Error,
    loginWithRedirect,
    logout: logoutAuth0,
    getAccessTokenSilently: getAuth0Token,
  } = useAuth0Hook();

  const getAccessTokenSilently = async (): Promise<string | null> => {
    try {
      return await getAuth0Token();
    } catch {
      return null;
    }
  };

  const refreshTokenSilently = async (): Promise<void> => {
    // Auth0 handles refresh automatically
    try {
      await getAuth0Token();
    } catch {
      // Silently fail, Auth0 will handle it
    }
  };

  const value: AuthContextType = {
    isAuthenticated: auth0IsAuthenticated,
    isLoading: auth0IsLoading,
    user: auth0User as User | null,
    error: auth0Error as Error | null,
    login: loginWithRedirect,
    logout: logoutAuth0,
    getAccessTokenSilently,
    refreshTokenSilently,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

/**
 * Main Auth Provider - uses backend for token exchange in all environments
 * Backend handles Okta token exchange securely with client secret
 */
export function AuthProvider({ children }: AuthProviderProps) {
  // Always use BackendAuthProvider so backend handles token exchange
  // This keeps the client secret secure on the server
  return <BackendAuthProvider>{children}</BackendAuthProvider>;
}

/**
 * Hook to use auth context
 */
export function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

/**
 * Helper function to check if user is admin
 */
export function isAdmin(user: User | null): boolean {
  return user?.role === 'ADMIN';
}

/**
 * Helper function to check if user is management
 */
export function isManagement(user: User | null): boolean {
  return user?.role === 'MANAGEMENT';
}

/**
 * Helper function to check if user has management access (MANAGEMENT or ADMIN)
 */
export function hasManagementAccess(user: User | null): boolean {
  return user?.role === 'MANAGEMENT' || user?.role === 'ADMIN';
}

/**
 * Helper function to check if user has admin access (ADMIN only)
 */
export function hasAdminAccess(user: User | null): boolean {
  return user?.role === 'ADMIN';
}

