import { createContext, useEffect, useState, useCallback, ReactNode, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { SSOClient } from '@lilith/sso-client';
import { authEvents } from './auth-events';
import type {
  AuthContextValue,
  AuthState,
  LoginCredentials,
  RegisterData,
  DirectRegisterData,
  User,
  DevAuthOverride,
} from './types';

interface AuthProviderProps {
  children: ReactNode;
  /**
   * SSO service URL (required)
   */
  ssoUrl: string;
  /**
   * Session check interval in ms (default: 300000 = 5 minutes)
   */
  checkInterval?: number;
  /**
   * Popup width for login/register (default: 500)
   */
  popupWidth?: number;
  /**
   * Popup height for login/register (default: 600)
   */
  popupHeight?: number;
  /**
   * Dev mode override - when provided, bypasses real SSO auth.
   * Only functional when import.meta.env.DEV is true.
   */
  devOverride?: DevAuthOverride;
}

export const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export function AuthProvider({
  children,
  ssoUrl,
  checkInterval = 300000,
  popupWidth = 500,
  popupHeight = 600,
  devOverride,
}: AuthProviderProps) {
  const queryClient = useQueryClient();
  const ssoClientRef = useRef<SSOClient | null>(null);

  // Check if dev override is active (only in dev mode)
  const isDevOverrideActive = import.meta.env.DEV && devOverride !== undefined;

  const [authState, setAuthState] = useState<AuthState>({
    user: null,
    isLoading: !isDevOverrideActive, // No loading when dev override
    isAuthenticated: false,
    error: null,
    isDevMode: false,
  });

  // Sync dev override to auth state
  useEffect(() => {
    if (isDevOverrideActive && devOverride) {
      setAuthState({
        user: devOverride.user,
        isLoading: false,
        isAuthenticated: devOverride.isAuthenticated,
        error: null,
        isDevMode: true,
      });
      // Also update query cache
      queryClient.setQueryData(['auth', 'me'], devOverride.user);
    }
  }, [isDevOverrideActive, devOverride, queryClient]);

  // Skip SSO client setup when dev override is active
  useEffect(() => {
    if (isDevOverrideActive) {
      return; // Don't initialize SSO client in dev override mode
    }

    const ssoClient = new SSOClient({
      ssoUrl,
      checkInterval,
      popupWidth,
      popupHeight,
      onAuthChange: (authenticated, user) => {
        setAuthState({
          user: user as User | null,
          isLoading: false,
          isAuthenticated: authenticated,
          error: null,
        });

        if (authenticated && user) {
          authEvents.broadcast({
            type: 'login',
            timestamp: Date.now(),
          });
        } else {
          authEvents.broadcast({
            type: 'logout',
            timestamp: Date.now(),
          });
        }

        queryClient.setQueryData(['auth', 'me'], user);
      },
      onError: (error) => {
        setAuthState((prev) => ({
          ...prev,
          error,
          isLoading: false,
        }));
      },
    });

    ssoClientRef.current = ssoClient;

    ssoClient.checkSession().then((response) => {
      setAuthState({
        user: response.user as User | null,
        isLoading: false,
        isAuthenticated: response.authenticated,
        error: null,
      });

      if (response.user) {
        queryClient.setQueryData(['auth', 'me'], response.user);
      }
    });

    ssoClient.startAutoCheck();

    authEvents.initialize();
    const unsubscribe = authEvents.subscribe((event) => {
      if (event.type === 'login' || event.type === 'token_refresh') {
        ssoClient.checkSession();
      } else if (event.type === 'logout') {
        setAuthState({
          user: null,
          isLoading: false,
          isAuthenticated: false,
          error: null,
        });
        queryClient.setQueryData(['auth', 'me'], null);
        queryClient.clear();
      }
    });

    return () => {
      ssoClient.destroy();
      unsubscribe();
      authEvents.destroy();
    };
  }, [ssoUrl, checkInterval, popupWidth, popupHeight, queryClient, isDevOverrideActive]);

  const login = useCallback(
    async (_credentials: LoginCredentials) => {
      if (!ssoClientRef.current) {
        throw new Error('SSO client not initialized');
      }

      setAuthState((prev) => ({ ...prev, isLoading: true, error: null }));

      try {
        const user = await ssoClientRef.current.login();
        setAuthState({
          user: user as User,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
        queryClient.setQueryData(['auth', 'me'], user);

        authEvents.broadcast({
          type: 'login',
          timestamp: Date.now(),
        });
      } catch (error) {
        setAuthState((prev) => ({
          ...prev,
          isLoading: false,
          error: error as Error,
        }));
        throw error;
      }
    },
    [queryClient]
  );

  const register = useCallback(
    async (data: RegisterData) => {
      if (!ssoClientRef.current) {
        throw new Error('SSO client not initialized');
      }

      setAuthState((prev) => ({ ...prev, isLoading: true, error: null }));

      try {
        const user = await ssoClientRef.current.register({ role: data.role });
        setAuthState({
          user: user as User,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
        queryClient.setQueryData(['auth', 'me'], user);

        authEvents.broadcast({
          type: 'login',
          timestamp: Date.now(),
        });
      } catch (error) {
        setAuthState((prev) => ({
          ...prev,
          isLoading: false,
          error: error as Error,
        }));
        throw error;
      }
    },
    [queryClient]
  );

  const loginWithCredentials = useCallback(
    async (email: string, password: string) => {
      if (!ssoClientRef.current) {
        throw new Error('SSO client not initialized');
      }

      setAuthState((prev) => ({ ...prev, isLoading: true, error: null }));

      try {
        const result = await ssoClientRef.current.loginWithCredentials(email, password);

        if (result.mfaRequired) {
          // MFA will be handled by the SSOClient's onMfaRequired callback
          setAuthState((prev) => ({ ...prev, isLoading: false }));
          return;
        }

        setAuthState({
          user: result.user as User,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
        queryClient.setQueryData(['auth', 'me'], result.user);

        authEvents.broadcast({
          type: 'login',
          timestamp: Date.now(),
        });
      } catch (error) {
        setAuthState((prev) => ({
          ...prev,
          isLoading: false,
          error: error as Error,
        }));
        throw error;
      }
    },
    [queryClient]
  );

  const registerWithCredentials = useCallback(
    async (data: DirectRegisterData) => {
      if (!ssoClientRef.current) {
        throw new Error('SSO client not initialized');
      }

      setAuthState((prev) => ({ ...prev, isLoading: true, error: null }));

      try {
        const result = await ssoClientRef.current.registerWithCredentials(
          data.email,
          data.username,
          data.password,
          data.role
        );
        setAuthState({
          user: result.user as User,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
        queryClient.setQueryData(['auth', 'me'], result.user);

        authEvents.broadcast({
          type: 'login',
          timestamp: Date.now(),
        });
      } catch (error) {
        setAuthState((prev) => ({
          ...prev,
          isLoading: false,
          error: error as Error,
        }));
        throw error;
      }
    },
    [queryClient]
  );

  const logout = useCallback(async () => {
    if (!ssoClientRef.current) {
      throw new Error('SSO client not initialized');
    }

    setAuthState((prev) => ({ ...prev, isLoading: true, error: null }));

    try {
      await ssoClientRef.current.logout();
      setAuthState({
        user: null,
        isLoading: false,
        isAuthenticated: false,
        error: null,
      });
      queryClient.setQueryData(['auth', 'me'], null);
      queryClient.clear();

      authEvents.broadcast({
        type: 'logout',
        timestamp: Date.now(),
      });
    } catch (error) {
      setAuthState((prev) => ({
        ...prev,
        isLoading: false,
        error: error as Error,
      }));
      throw error;
    }
  }, [queryClient]);

  const refreshAuth = useCallback(async () => {
    if (!ssoClientRef.current) {
      throw new Error('SSO client not initialized');
    }

    const response = await ssoClientRef.current.checkSession();
    setAuthState({
      user: response.user as User | null,
      isLoading: false,
      isAuthenticated: response.authenticated,
      error: null,
    });

    if (response.user) {
      queryClient.setQueryData(['auth', 'me'], response.user);
    }
  }, [queryClient]);

  const contextValue: AuthContextValue = {
    ...authState,
    login,
    register,
    loginWithCredentials,
    registerWithCredentials,
    logout,
    refreshAuth,
  };

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