/**
 * Authentication Context
 *
 * Provides global authentication state management for the application.
 * This context integrates with the auth services to provide a clean
 * interface for authentication operations throughout the app.
 *
 * @example
 * ```typescript
 * import { AuthProvider } from '@/context/AuthContext';
 *
 * function App() {
 *   return (
 *     <AuthProvider>
 *       <YourAppComponents />
 *     </AuthProvider>
 *   );
 * }
 * ```
 */

'use client';

import React, { createContext, useEffect, useState, ReactNode } from 'react';
import { usePathname } from 'next/navigation';
import { authStorageService } from '@/services/auth/storageService';
import { isProfileComplete } from '@/lib/utils.client';
import { normalizeAuthUser } from '@/lib/auth/normalize-user';
import type { User, AuthState, AuthContextType } from '@/types/auth';

// Create the context with proper typing
export const AuthContext = createContext<AuthContextType | null>(null);

/**
 * Authentication Provider Component
 *
 * Provides authentication state and operations to the entire application.
 * Manages user state, profile completeness, and authentication operations.
 */
export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const pathname = usePathname();
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  /**
   * Login user and store in localStorage
   *
   * Optimistic update: Updates React state immediately and persists to localStorage
   * Graceful degradation: Continues even if localStorage fails
   */
  const login = React.useCallback((userData: User) => {
    const normalizedUser = normalizeAuthUser(userData);
    if (!normalizedUser) return;

    // 1. Update React state immediately (optimistic update for instant UX)
    setUser(normalizedUser);
    setError(null);

    // 2. Persist to localStorage (sync)
    try {
      authStorageService.setUser(normalizedUser);
      // Verify that data was actually saved
      const saved = authStorageService.getUser();
      if (!saved || saved.id !== normalizedUser.id) {
        console.error('[AuthContext] User data was not saved correctly to localStorage');
      }
    } catch (error) {
      console.error('[AuthContext] Failed to save user to localStorage:', error);
      // Don't fail login if localStorage fails - continue anyway
      // But log the error for debugging
    }
  }, []);

  /**
   * Logout user and clear storage
   *
   * Clears state and localStorage immediately, then calls the local route to
   * expire the httpOnly auth cookies. The local route does not call the backend.
   */
  const logout = React.useCallback(async () => {
    // 1. Optimistic: Clear state immediately for instant UX
    setUser(null);
    setError(null);

    // 2. Clear localStorage immediately
    try {
      authStorageService.clearAll();
    } catch (error) {
      console.warn('[AuthContext] Failed to clear localStorage:', error);
      // Continue anyway
    }

    // 3. Expire httpOnly cookies through the local Next.js route
    try {
      const res = await fetch('/api/auth/logout', {
        method: 'POST',
      });
      // Ignore response - already cleared locally
      if (!res.ok) {
        console.warn('[AuthContext] Logout API call failed but state already cleared');
      }
    } catch (error) {
      console.warn('[AuthContext] Logout API call failed:', error);
      // Already cleared, continue anyway
    }
  }, []);

  /**
   * Internal function to get current user with error details
   * Used internally by refreshUser to distinguish between auth and network errors
   */
  const getCurrentUserWithError = React.useCallback(async (): Promise<{
    user: User | null;
    error: 'UNAUTHORIZED' | 'NETWORK' | null;
  }> => {
    try {
      const userData = await fetch('/api/auth/me', {
        method: 'GET',
        credentials: 'include', // Include cookies
      });

      // Check for 401 Unauthorized - token expired or invalid
      if (userData.status === 401) {
        return { user: null, error: 'UNAUTHORIZED' };
      }

      if (!userData.ok) {
        // Other errors (network, server error, etc.)
        console.error(
          '[AuthContext] Failed to get current user:',
          userData.status,
          userData.statusText,
        );
        return { user: null, error: 'NETWORK' };
      }

      const data = await userData.json();
      // Handle different response formats: { user: ... } or { ok: true, user: ... }
      const user = normalizeAuthUser((data?.user || data?.result?.user) as User | null);

      if (user) {
        // Update state
        setUser(user);
        setError(null);

        // Persist to localStorage
        try {
          authStorageService.setUser(user);
        } catch (error) {
          console.warn('[AuthContext] Failed to save user to localStorage:', error);
        }

        return { user, error: null };
      }

      return { user: null, error: 'NETWORK' };
    } catch (error) {
      console.error('[AuthContext] Failed to get current user:', error);
      return { user: null, error: 'NETWORK' };
    }
  }, []);

  /**
   * Get current user from backend
   *
   * Public API: Returns User | null (for compatibility with AuthContextType)
   * Fetches user data from server and updates both state and localStorage
   */
  const getCurrentUser = React.useCallback(async (): Promise<User | null> => {
    const result = await getCurrentUserWithError();
    return result.user;
  }, [getCurrentUserWithError]);

  /**
   * Refresh user data from storage
   *
   * Hybrid strategy:
   * 1. Load from localStorage first (instant UX)
   * 2. Sync with server in background (ensure data freshness)
   * 3. Graceful degradation: Use cached data if server sync fails
   * 4. Only clear localStorage if token is truly expired (401), not on network errors
   */
  const refreshUser = React.useCallback(async () => {
    setIsLoading(true);

    try {
      // Strategy 1: Load from localStorage first (instant UX)
      const stored = authStorageService.getUser();
      if (stored) {
        setUser(stored); // Show cached data immediately
        setIsLoading(false); // User sees data immediately
      }

      // Strategy 2: Sync with server in background
      try {
        const result = await getCurrentUserWithError();

        if (result.user) {
          // User data retrieved successfully
          // Only update if user data actually changed (avoid unnecessary re-renders)
          const stored = authStorageService.getUser();
          const storedJson = stored ? JSON.stringify(stored) : null;
          const userJson = JSON.stringify(result.user);
          const hasChanged = storedJson !== userJson;

          if (hasChanged || !stored) {
            setUser(result.user);
            try {
              authStorageService.setUser(result.user);
            } catch (error) {
              console.warn('[AuthContext] Failed to update localStorage:', error);
            }
          }
          // If no change, keep using cached data (already set above)
        } else if (result.error === 'UNAUTHORIZED') {
          // Token expired or invalid (401) - clear cache
          console.warn('[AuthContext] User unauthorized, clearing cached data');
          setUser(null);
          setError('Session expired. Please login again.');
          try {
            authStorageService.clearAll();
          } catch (error) {
            console.warn('[AuthContext] Failed to clear localStorage:', error);
          }
        } else if (result.error === 'NETWORK' && stored) {
          // Network error but we have cached data - keep using it
          // Don't clear localStorage on network errors
          console.debug('[AuthContext] Server sync failed (network error), using cached data');
          // User already set from cached data above
        } else if (result.error === 'NETWORK' && !stored) {
          // Network error and no cached data - show error
          console.error('[AuthContext] Failed to load user (network error)');
          setError('Failed to load user data. Please check your connection.');
        } else if (!result.error && result.user) {
          // Success - clear any previous errors
          setError(null);
        }
      } catch (syncError) {
        // Unexpected error - keep using cached data if available
        if (!stored) {
          console.error('[AuthContext] Failed to refresh user:', syncError);
          setError('Failed to load user data');
        } else {
          // Silent fail - keep using cached data
          console.debug('[AuthContext] Server sync failed, using cached data:', syncError);
          // Clear error if we have cached data to use
          setError(null);
        }
      }

      // Clear error if no error occurred
      if (!error) {
        setError(null);
      }
    } catch (error) {
      console.error('[AuthContext] Failed to refresh user:', error);
      setError('Failed to load user data');
    } finally {
      setIsLoading(false);
    }
  }, [getCurrentUserWithError]);

  /**
   * Update user profile data
   *
   * Optimistic update: Updates state and localStorage immediately
   * Should be called after server confirms update for consistency
   */
  const updateProfile = React.useCallback(
    (data: Partial<User>) => {
      if (!user) return;

      // 1. Optimistic update: Update React state immediately
      const updatedUser = normalizeAuthUser({ ...user, ...data } as User);
      if (!updatedUser) return;
      setUser(updatedUser);

      // 2. Persist to localStorage immediately
      try {
        authStorageService.setUser(updatedUser);
      } catch (error) {
        console.warn('[AuthContext] Failed to update localStorage:', error);
        // Don't fail if localStorage update fails
      }
    },
    [user],
  );

  // Load user data on mount, except auth pages where /me is unnecessary.
  useEffect(() => {
    if (
      pathname === '/signin' ||
      pathname === '/signup' ||
      pathname === '/check-email' ||
      pathname === '/verify-email' ||
      pathname === '/forgot-password' ||
      pathname === '/reset-password'
    ) {
      setIsLoading(false);
      return;
    }
    refreshUser();
  }, [refreshUser, pathname]);

  // Migrate old storage format if needed
  useEffect(() => {
    authStorageService.migrate();
  }, []);

  // Computed values
  const isAuthenticated = !!user;
  const isComplete = isProfileComplete(user);

  // Listen untuk token expired event (dispatched by on-demand refresh)
  useEffect(() => {
    const handleTokenExpired = () => {
      // 1. Clear user state immediately
      setUser(null);
      setError(null);

      // 2. Clear localStorage
      try {
        authStorageService.clearAll();
      } catch (error) {
        // Silent fail
      }

      // Note: logout() will be called which also clears everything,
      // but we do it here first for immediate feedback
      logout();
    };

    window.addEventListener('auth:token-expired', handleTokenExpired);
    return () => {
      window.removeEventListener('auth:token-expired', handleTokenExpired);
    };
  }, [logout]);

  // Listen untuk user updated event (for cross-component sync)
  useEffect(() => {
    const handleUserUpdated = (event: CustomEvent<{ user: User }>) => {
      const user = normalizeAuthUser(event.detail.user);
      if (!user) return;
      setUser(user);
      try {
        authStorageService.setUser(user);
      } catch (error) {
        console.warn('[AuthContext] Failed to update localStorage from event:', error);
      }
    };

    window.addEventListener('auth:user-updated', handleUserUpdated as EventListener);
    return () => {
      window.removeEventListener('auth:user-updated', handleUserUpdated as EventListener);
    };
  }, []);

  // Create auth state object
  const state: AuthState = {
    user,
    isAuthenticated,
    isProfileComplete: isComplete,
    isLoading,
    error,
  };

  // Context value
  const contextValue: AuthContextType = React.useMemo(
    () => ({
      state,
      login,
      logout,
      getCurrentUser,
      refreshUser,
      updateProfile,
    }),
    [
      state,
      login,
      logout,
      getCurrentUser,
      refreshUser,
      updateProfile,
    ],
  );

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