"use client";

/**
 * Authentication Context Provider
 *
 * Provides authentication state and methods throughout the app
 * - User state management
 * - Login/logout functionality
 * - Token management
 * - Protected route checking
 */

import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
import { useRouter } from '@/i18n/routing';
import apiClient, { handleApiError } from '@/lib/api/client';
import type { User, LoginRequest, LoginResponse } from '@/lib/api/types';
import {
  AUTH_SESSION_EXPIRED_EVENT,
  type AuthSessionExpiredDetail,
} from '@/lib/auth-events';
import { setAuthTokens, clearAuthTokens, getAuthToken } from '@/lib/auth-cookies';
import { clearAllRbacCaches } from '@/lib/rbac/cache';
import { clearRbacRuntimeState } from '@/lib/rbac/runtime';
import { detectLocale, stripLocalePrefix, buildLocalizedPath } from '@/lib/locale';

interface AuthContextType {
  user: User | null;
  isLoading: boolean;
  isAuthenticated: boolean;
  login: (credentials: LoginRequest) => Promise<void>;
  loginWithTokens: (token: string, refreshToken: string | undefined, user: User) => void;
  logout: () => void;
  refreshUser: () => Promise<void>;
  updateUser: (patch: Partial<User>) => void;
}

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

interface AuthProviderProps {
  children: React.ReactNode;
}

const AUTH_PUBLIC_ROUTES = new Set(['/login', '/register', '/forgot-password']);

function normalizeRedirectPath(path: string | null): string | null {
  if (!path) {
    return null;
  }

  let decoded = path;
  try {
    decoded = decodeURIComponent(path);
  } catch {
    decoded = path;
  }

  if (!decoded.startsWith('/') || decoded.startsWith('//')) {
    return null;
  }

  return decoded;
}

export function AuthProvider({ children }: AuthProviderProps) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const sessionRedirectingRef = useRef(false);
  const router = useRouter();

  const resolvePostLoginPath = useCallback((preferredLang: string): string => {
    if (typeof window === 'undefined') {
      return '/';
    }

    const currentUrl = new URL(window.location.href);
    const queryRedirect = normalizeRedirectPath(currentUrl.searchParams.get('redirect'));
    const storedRedirect = normalizeRedirectPath(sessionStorage.getItem('redirect_after_login'));
    if (storedRedirect) {
      sessionStorage.removeItem('redirect_after_login');
    }

    const redirectTarget = queryRedirect || storedRedirect;
    if (redirectTarget) {
      return buildLocalizedPath(redirectTarget, preferredLang);
    }

    const currentPathWithoutLocale = stripLocalePrefix(currentUrl.pathname);
    if (AUTH_PUBLIC_ROUTES.has(currentPathWithoutLocale)) {
      return buildLocalizedPath("/", preferredLang);
    }

    const currentFullPath = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
    return buildLocalizedPath(currentFullPath, preferredLang);
  }, []);

  const redirectToLogin = useCallback(() => {
    if (typeof window === 'undefined') {
      router.push('/login');
      return;
    }

    const { pathname, search, hash, origin } = window.location;
    const locale = detectLocale(pathname);
    const pathWithoutLocale = stripLocalePrefix(pathname);
    const loginPath = buildLocalizedPath("/login", locale);

    if (AUTH_PUBLIC_ROUTES.has(pathWithoutLocale)) {
      sessionRedirectingRef.current = false;
      return;
    }

    const loginUrl = new URL(loginPath, origin);
    loginUrl.searchParams.set('redirect', `${pathname}${search}${hash}`);
    window.location.replace(loginUrl.toString());
  }, [router]);

  /**
   * Fetch current user from the API
   */
  const fetchUser = useCallback(async () => {
    try {
      const token = getAuthToken();
      if (!token) {
        setIsLoading(false);
        return;
      }

      // API returns { success, message, data: User }
      const response = await apiClient.get<{ success: boolean; message: string | null; data: User }>('/auth/me');
      setUser(response.data.data);
    } catch {
      // Token is invalid, clear it
      clearAuthTokens();
      clearAllRbacCaches();
      clearRbacRuntimeState();
      setUser(null);
    } finally {
      setIsLoading(false);
    }
  }, []);

  /**
   * Initialize auth state on mount
   */
  useEffect(() => {
    fetchUser();
  }, [fetchUser]);

  useEffect(() => {
    if (typeof window === 'undefined') {
      return;
    }

    const handleSessionExpired = (event: Event) => {
      if (sessionRedirectingRef.current) {
        return;
      }

      const customEvent = event as CustomEvent<AuthSessionExpiredDetail>;
      const source = customEvent.detail?.source;

      sessionRedirectingRef.current = true;
      clearAuthTokens();
      clearAllRbacCaches();
      clearRbacRuntimeState();
      setUser(null);
      setIsLoading(false);

      if (source === 'manual') {
        sessionRedirectingRef.current = false;
        return;
      }

      redirectToLogin();
    };

    window.addEventListener(AUTH_SESSION_EXPIRED_EVENT, handleSessionExpired as EventListener);
    return () => {
      window.removeEventListener(AUTH_SESSION_EXPIRED_EVENT, handleSessionExpired as EventListener);
    };
  }, [redirectToLogin]);

  /**
   * Login user with credentials
   */
  const login = async (credentials: LoginRequest) => {
    try {
      setIsLoading(true);

      const response = await apiClient.post<LoginResponse>('/auth/login', credentials);

      // Handle nested data structure from API
      const { token, refresh_token, user: userData } = response.data.data;

      // Store tokens in both localStorage and cookies
      setAuthTokens(token, refresh_token);

      // Set user state
      setUser(userData);

      // Switch to user's preferred language if set
      const preferredLang = userData.preferred_language || 'ar';

      // Redirect to intended page after login (or fallback to dashboard) using preferred language.
      if (typeof window !== 'undefined') {
        const destination = resolvePostLoginPath(preferredLang);
        window.location.href = destination;
      } else {
        router.push('/');
      }
    } catch (error) {
      const errorMessage = handleApiError(error);
      throw new Error(errorMessage);
    } finally {
      setIsLoading(false);
    }
  };

  /**
   * Logout user
   */
  const logout = useCallback(async () => {
    try {
      // Call logout endpoint to invalidate token on server
      await apiClient.post('/auth/logout');
    } catch {
      // Ignore logout errors - we'll clear local state anyway
    } finally {
      // Clear tokens from both localStorage and cookies
      clearAuthTokens();
      clearAllRbacCaches();
      clearRbacRuntimeState();
      setUser(null);
      sessionRedirectingRef.current = false;
      const loginPath = buildLocalizedPath("/login", "ar");
      router.push(loginPath);
    }
  }, [router]);

  /**
   * Login with tokens directly (for OTP flow)
   * This method stores tokens and user data without making an API call
   */
  const loginWithTokens = useCallback((token: string, refreshToken: string | undefined, userData: User) => {
    // Store tokens in both localStorage and cookies
    setAuthTokens(token, refreshToken);

    // Set user state
    setUser(userData);

    // Switch to user's preferred language if set
    const preferredLang = userData.preferred_language || 'ar';

    // Redirect to intended page after login (or fallback to dashboard) using preferred language.
    if (typeof window !== 'undefined') {
      const destination = resolvePostLoginPath(preferredLang);
      window.location.href = destination;
    } else {
      router.push('/');
    }
  }, [resolvePostLoginPath, router]);

  /**
   * Refresh user data
   */
  const refreshUser = useCallback(async () => {
    await fetchUser();
  }, [fetchUser]);

  const updateUser = useCallback((patch: Partial<User>) => {
    setUser((currentUser) => {
      if (!currentUser) return currentUser;

      return {
        ...currentUser,
        ...patch,
      };
    });
  }, []);

  const value: AuthContextType = {
    user,
    isLoading,
    isAuthenticated: !!user,
    login,
    loginWithTokens,
    logout,
    refreshUser,
    updateUser,
  };

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

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