import * as React from 'react';
import { createContext, useContext, useCallback, useMemo, useEffect, useState } from 'react';
import type { User, AuthState, LoginCredentials } from './types';

const TOKEN_STORAGE_KEY = 'auth_token';
const USER_STORAGE_KEY = 'auth_user';

/**
 * Authentication context value.
 */
export interface AuthContextValue extends AuthState {
  /** Log in with credentials */
  login: (credentials: LoginCredentials) => Promise<void>;
  /** Log in with a token directly */
  loginWithToken: (token: string, user?: User) => void;
  /** Log out the current user */
  logout: () => void;
  /** Get the current access token */
  getToken: () => string | null;
  /** Check if user has a specific role */
  hasRole: (role: string) => boolean;
  /** Check if user has a specific scope */
  hasScope: (scope: string) => boolean;
}

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

/**
 * Auth provider configuration.
 */
export interface AuthProviderProps {
  children: React.ReactNode;
  /** API endpoint for login */
  loginUrl?: string;
  /** API endpoint for logout */
  logoutUrl?: string;
  /** API endpoint for fetching current user */
  userUrl?: string;
  /** Custom login handler */
  onLogin?: (credentials: LoginCredentials) => Promise<{ token: string; user: User }>;
  /** Custom logout handler */
  onLogout?: () => Promise<void>;
  /** Storage type for persisting auth state */
  storage?: 'localStorage' | 'sessionStorage' | 'none';
}

/**
 * AuthProvider manages authentication state and provides auth methods.
 *
 * @example
 * // Basic usage
 * <AuthProvider loginUrl="/api/auth/login">
 *   <App />
 * </AuthProvider>
 *
 * @example
 * // With custom handlers
 * <AuthProvider
 *   onLogin={async (creds) => {
 *     const res = await myAuthService.login(creds);
 *     return { token: res.token, user: res.user };
 *   }}
 * >
 *   <App />
 * </AuthProvider>
 */
export function AuthProvider({
  children,
  loginUrl = '/api/auth/login',
  logoutUrl = '/api/auth/logout',
  userUrl = '/api/auth/me',
  onLogin,
  onLogout,
  storage = 'localStorage',
}: AuthProviderProps) {
  const [state, setState] = useState<AuthState>({
    user: null,
    isLoading: true,
    isAuthenticated: false,
    error: null,
  });

  // Get storage implementation
  const getStorage = useCallback(() => {
    if (storage === 'none') return null;
    return storage === 'sessionStorage' ? sessionStorage : localStorage;
  }, [storage]);

  // Initialize auth state from storage
  useEffect(() => {
    const store = getStorage();
    if (!store) {
      setState((s) => ({ ...s, isLoading: false }));
      return;
    }

    const token = store.getItem(TOKEN_STORAGE_KEY);
    const userJson = store.getItem(USER_STORAGE_KEY);

    if (token && userJson) {
      try {
        const user = JSON.parse(userJson) as User;
        setState({
          user,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
      } catch {
        // Invalid stored data, clear it
        store.removeItem(TOKEN_STORAGE_KEY);
        store.removeItem(USER_STORAGE_KEY);
        setState((s) => ({ ...s, isLoading: false }));
      }
    } else {
      setState((s) => ({ ...s, isLoading: false }));
    }
  }, [getStorage]);

  // Login with credentials
  const login = useCallback(
    async (credentials: LoginCredentials) => {
      setState((s) => ({ ...s, isLoading: true, error: null }));

      try {
        let token: string;
        let user: User;

        if (onLogin) {
          // Use custom login handler
          const result = await onLogin(credentials);
          token = result.token;
          user = result.user;
        } else {
          // Use default API login
          const response = await fetch(loginUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(credentials),
          });

          if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw new Error(error.message || 'Login failed');
          }

          const data = await response.json();
          token = data.data?.token || data.token;
          user = data.data?.user || data.user;
        }

        // Store token and user
        const store = getStorage();
        if (store) {
          store.setItem(TOKEN_STORAGE_KEY, token);
          store.setItem(USER_STORAGE_KEY, JSON.stringify(user));
        }

        setState({
          user,
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
      } catch (error) {
        setState({
          user: null,
          isLoading: false,
          isAuthenticated: false,
          error: error instanceof Error ? error : new Error('Login failed'),
        });
        throw error;
      }
    },
    [loginUrl, onLogin, getStorage]
  );

  // Login with token directly
  const loginWithToken = useCallback(
    (token: string, user?: User) => {
      const store = getStorage();
      if (store) {
        store.setItem(TOKEN_STORAGE_KEY, token);
        if (user) {
          store.setItem(USER_STORAGE_KEY, JSON.stringify(user));
        }
      }

      setState({
        user: user || null,
        isLoading: false,
        isAuthenticated: true,
        error: null,
      });
    },
    [getStorage]
  );

  // Logout
  const logout = useCallback(async () => {
    try {
      if (onLogout) {
        await onLogout();
      } else if (logoutUrl) {
        await fetch(logoutUrl, { method: 'POST' }).catch(() => {});
      }
    } finally {
      const store = getStorage();
      if (store) {
        store.removeItem(TOKEN_STORAGE_KEY);
        store.removeItem(USER_STORAGE_KEY);
      }

      setState({
        user: null,
        isLoading: false,
        isAuthenticated: false,
        error: null,
      });
    }
  }, [logoutUrl, onLogout, getStorage]);

  // Get token
  const getToken = useCallback(() => {
    const store = getStorage();
    return store ? store.getItem(TOKEN_STORAGE_KEY) : null;
  }, [getStorage]);

  // Role check
  const hasRole = useCallback(
    (role: string) => {
      return state.user?.roles?.includes(role) ?? false;
    },
    [state.user]
  );

  // Scope check
  const hasScope = useCallback(
    (scope: string) => {
      return state.user?.scopes?.includes(scope) ?? false;
    },
    [state.user]
  );

  const value = useMemo(
    (): AuthContextValue => ({
      ...state,
      login,
      loginWithToken,
      logout,
      getToken,
      hasRole,
      hasScope,
    }),
    [state, login, loginWithToken, logout, getToken, hasRole, hasScope]
  );

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

/**
 * Hook to access authentication state and methods.
 *
 * @example
 * function Profile() {
 *   const { user, logout, isAuthenticated } = useAuth();
 *
 *   if (!isAuthenticated) {
 *     return <LoginForm />;
 *   }
 *
 *   return (
 *     <div>
 *       <p>Welcome, {user?.name}</p>
 *       <button onClick={logout}>Logout</button>
 *     </div>
 *   );
 * }
 */
export function useAuth(): AuthContextValue {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}
