/**
 * Authentication Context for ATProtocol OAuth
 * Manages auth state, login/logout, and token refresh
 */

import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  useCallback,
  ReactNode,
} from 'react';

import { ATProtoOAuthClient } from '../lib/atproto-oauth-client';
import { XRPCClient } from '../lib/xrpc-client';

import type {
  OAuthSession,
  UserProfile,
  AuthContextType,
} from '../types/auth';

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

interface AuthProviderProps {
  children: ReactNode;
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  const [user, setUser] = useState<UserProfile | null>(null);
  const [session, setSession] = useState<OAuthSession | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  // Create stable client instances (only once)
  const [oauthClient] = useState(() => new ATProtoOAuthClient());
  const [xrpcClient] = useState(() => new XRPCClient());

  /**
   * Initialize auth state on mount
   */
  useEffect(() => {
    const initAuth = async () => {
      try {
        const storedSession = await oauthClient.getSession();

        if (storedSession) {
          // Session exists, restore user from session data
          setSession(storedSession);
          setUser({
            did: storedSession.did,
            handle: storedSession.handle,
          });
        }
      } catch (err) {
        console.error('Auth initialization error:', err);
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    };

    initAuth();
  }, [oauthClient, xrpcClient]);

  /**
   * Start OAuth login flow
   * @param handle - User's handle
   */
  const login = useCallback(async (handle: string) => {
    try {
      setLoading(true);
      setError(null);

      const authUrl = await oauthClient.authorize(handle);

      // Redirect to authorization server
      window.location.href = authUrl;
    } catch (err) {
      console.error('Login error:', err);
      setError(err as Error);
      setLoading(false);
      throw err;
    }
  }, [oauthClient]);

  /**
   * Handle OAuth callback
   * @param code - Authorization code
   * @param state - State parameter
   * @param iss - Optional issuer parameter
   */
  const handleCallback = useCallback(async (
    code: string,
    state: string,
    iss?: string
  ) => {
    try {
      setLoading(true);
      setError(null);

      const newSession = await oauthClient.callback(code, state, iss);

      // Set session and user from the session data
      setSession(newSession);
      setUser({
        did: newSession.did,
        handle: newSession.handle,
      });
    } catch (err) {
      console.error('Callback error:', err);
      setError(err as Error);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [oauthClient, xrpcClient]);

  /**
   * Logout and clear session
   */
  const logout = useCallback(async () => {
    try {
      setLoading(true);

      await oauthClient.clearSession();

      setUser(null);
      setSession(null);
      setError(null);
    } catch (err) {
      console.error('Logout error:', err);
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  }, [oauthClient]);

  /**
   * Manually refresh access token
   */
  const refreshToken = useCallback(async () => {
    if (!session) {
      throw new Error('No active session');
    }

    try {
      const newSession = await oauthClient.refreshAccessToken(session);
      setSession(newSession);
    } catch (err) {
      console.error('Token refresh error:', err);
      setError(err as Error);
      throw err;
    }
  }, [session, oauthClient]);

  /**
   * Auto-refresh token before expiry
   */
  useEffect(() => {
    if (!session) {
      return;
    }

    // Calculate time until token expiry
    const timeUntilExpiry = session.expiresAt - Date.now();

    // If token is already expired or expiring in less than 1 minute
    if (timeUntilExpiry <= 60000) {
      refreshToken().catch((err) => {
        console.error('Auto-refresh failed:', err);
        // If refresh fails, clear session
        logout();
      });
      return;
    }

    // Set timeout to refresh 1 minute before expiry
    const refreshTimeout = setTimeout(() => {
      refreshToken().catch((err) => {
        console.error('Auto-refresh failed:', err);
        logout();
      });
    }, timeUntilExpiry - 60000);

    return () => clearTimeout(refreshTimeout);
  }, [session, refreshToken, logout]);

  const contextValue: AuthContextType = {
    user,
    session,
    loading,
    error,
    isAuthenticated: !!user && !!session,
    login,
    handleCallback,
    logout,
    refreshToken,
  };

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

/**
 * Hook to use auth context
 * @returns Auth context value
 */
export const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

/**
 * Get XRPC client with current session
 * @returns Configured XRPC client
 */
export const useXRPC = (): XRPCClient | null => {
  const { session } = useAuth();

  if (!session) {
    return null;
  }

  const client = new XRPCClient();
  client.setSession(session);
  return client;
};