import {
  useQuery,
  useMutation,
  useQueryClient,
  type UseQueryOptions,
  type UseMutationOptions,
  type QueryKey,
} from '@tanstack/react-query'

/**
 * API interface for CRUD operations
 * Implement this interface for your entity's API client
 */
export interface CrudApi<TEntity, TCreateDto = Partial<TEntity>, TUpdateDto = Partial<TEntity>> {
  getAll: () => Promise<TEntity[]>;
  getById: (id: string) => Promise<TEntity>;
  create: (data: TCreateDto) => Promise<TEntity>;
  update: (id: string, data: TUpdateDto) => Promise<TEntity>;
  delete: (id: string) => Promise<void>;
}

/**
 * Options for CRUD hook generation
 */
export interface CreateCrudHooksOptions<TEntity, TCreateDto = Partial<TEntity>, TUpdateDto = Partial<TEntity>> {
  /**
   * Base query key for this entity
   * @example ['users'], ['products'], ['orders']
   */
  queryKey: QueryKey;

  /**
   * API client implementing CRUD operations
   */
  api: CrudApi<TEntity, TCreateDto, TUpdateDto>;

  /**
   * Enable optimistic updates (default: false)
   * When enabled, mutations will update the cache immediately
   */
  enableOptimistic?: boolean;

  /**
   * Custom query options for getAll
   */
  getAllOptions?: Omit<UseQueryOptions<TEntity[]>, 'queryKey' | 'queryFn'>;

  /**
   * Custom query options for getById
   */
  getByIdOptions?: Omit<UseQueryOptions<TEntity>, 'queryKey' | 'queryFn'>;
}

/**
 * Generated CRUD hooks
 */
export interface CrudHooks<TEntity, TCreateDto = Partial<TEntity>, TUpdateDto = Partial<TEntity>> {
  /**
   * Hook to fetch all entities
   * @example
   * ```typescript
   * const { data: users, isLoading } = useGetAll();
   * ```
   */
  useGetAll: (options?: Omit<UseQueryOptions<TEntity[]>, 'queryKey' | 'queryFn'>) => ReturnType<typeof useQuery<TEntity[]>>;

  /**
   * Hook to fetch a single entity by ID
   * @example
   * ```typescript
   * const { data: user } = useGetById('123');
   * ```
   */
  useGetById: (id: string, options?: Omit<UseQueryOptions<TEntity>, 'queryKey' | 'queryFn'>) => ReturnType<typeof useQuery<TEntity>>;

  /**
   * Hook to create a new entity
   * @example
   * ```typescript
   * const { mutate: createUser } = useCreate();
   * createUser({ name: 'John', email: 'john@example.com' });
   * ```
   */
  useCreate: (options?: UseMutationOptions<TEntity, Error, TCreateDto>) => ReturnType<typeof useMutation<TEntity, Error, TCreateDto>>;

  /**
   * Hook to update an existing entity
   * @example
   * ```typescript
   * const { mutate: updateUser } = useUpdate();
   * updateUser({ id: '123', data: { name: 'Jane' } });
   * ```
   */
  useUpdate: (options?: UseMutationOptions<TEntity, Error, { id: string; data: TUpdateDto }>) => ReturnType<typeof useMutation<TEntity, Error, { id: string; data: TUpdateDto }>>;

  /**
   * Hook to delete an entity
   * @example
   * ```typescript
   * const { mutate: deleteUser } = useDelete();
   * deleteUser('123');
   * ```
   */
  useDelete: (options?: UseMutationOptions<void, Error, string>) => ReturnType<typeof useMutation<void, Error, string>>;
}

/**
 * Create a set of CRUD hooks for an entity
 *
 * Generates standard useQuery and useMutation hooks with automatic
 * cache invalidation and optional optimistic updates.
 *
 * @example
 * ```typescript
 * // Define your API
 * const userApi: CrudApi<User, CreateUserDto, UpdateUserDto> = {
 *   getAll: () => apiClient.get('/users').then(r => r.data),
 *   getById: (id) => apiClient.get(`/users/${id}`).then(r => r.data),
 *   create: (data) => apiClient.post('/users', data).then(r => r.data),
 *   update: (id, data) => apiClient.patch(`/users/${id}`, data).then(r => r.data),
 *   delete: (id) => apiClient.delete(`/users/${id}`).then(r => r.data),
 * };
 *
 * // Generate hooks
 * const {
 *   useGetAll,
 *   useGetById,
 *   useCreate,
 *   useUpdate,
 *   useDelete,
 * } = createCrudHooks({
 *   queryKey: ['users'],
 *   api: userApi,
 *   enableOptimistic: true,
 * });
 *
 * // Use in components
 * function UserList() {
 *   const { data: users, isLoading } = useGetAll();
 *   const { mutate: createUser } = useCreate();
 *   const { mutate: deleteUser } = useDelete();
 *
 *   return (
 *     // ... UI code
 *   );
 * }
 * ```
 */
export function createCrudHooks<TEntity, TCreateDto = Partial<TEntity>, TUpdateDto = Partial<TEntity>>(
  options: CreateCrudHooksOptions<TEntity, TCreateDto, TUpdateDto>
): CrudHooks<TEntity, TCreateDto, TUpdateDto> {
  const { queryKey, api, enableOptimistic = false, getAllOptions, getByIdOptions } = options

  // Hook: Get all entities
  function useGetAll(customOptions?: Omit<UseQueryOptions<TEntity[]>, 'queryKey' | 'queryFn'>) {
    return useQuery<TEntity[]>({
      queryKey,
      queryFn: api.getAll,
      ...getAllOptions,
      ...customOptions,
    })
  }

  // Hook: Get entity by ID
  function useGetById(id: string, customOptions?: Omit<UseQueryOptions<TEntity>, 'queryKey' | 'queryFn'>) {
    return useQuery<TEntity>({
      queryKey: [...queryKey, id],
      queryFn: () => api.getById(id),
      enabled: !!id,
      ...getByIdOptions,
      ...customOptions,
    })
  }

  // Hook: Create entity
  function useCreate(customOptions?: UseMutationOptions<TEntity, Error, TCreateDto>) {
    const queryClient = useQueryClient()

    return useMutation<TEntity, Error, TCreateDto>({
      mutationFn: api.create,
      onSuccess: (...args) => {
        // Invalidate list query to refetch
        queryClient.invalidateQueries({ queryKey })

        // Call custom onSuccess if provided
        if (customOptions?.onSuccess) {
          customOptions.onSuccess(...args)
        }
      },
      ...customOptions,
    })
  }

  // Hook: Update entity
  function useUpdate(customOptions?: UseMutationOptions<TEntity, Error, { id: string; data: TUpdateDto }>) {
    const queryClient = useQueryClient()

    return useMutation<TEntity, Error, { id: string; data: TUpdateDto }>({
      mutationFn: ({ id, data }) => api.update(id, data),
      onMutate: enableOptimistic
        ? async ({ id, data }) => {
            // Cancel outgoing refetches
            await queryClient.cancelQueries({ queryKey: [...queryKey, id] })

            // Snapshot previous value
            const previous = queryClient.getQueryData<TEntity>([...queryKey, id])

            // Optimistically update
            if (previous) {
              queryClient.setQueryData<TEntity>([...queryKey, id], {
                ...previous,
                ...data,
              } as TEntity)
            }

            return { previous }
          }
        : customOptions?.onMutate,
      onError: enableOptimistic
        ? (...args) => {
            // Rollback on error
            const [, variables] = args
            const context = { previous: queryClient.getQueryData<TEntity>([...queryKey, variables.id]) }
            if (context?.previous) {
              queryClient.setQueryData([...queryKey, variables.id], context.previous)
            }
            if (customOptions?.onError) {
              customOptions.onError(...args)
            }
          }
        : customOptions?.onError,
      onSuccess: (...args) => {
        // Invalidate queries
        const [, variables] = args
        queryClient.invalidateQueries({ queryKey })
        queryClient.invalidateQueries({ queryKey: [...queryKey, variables.id] })

        if (customOptions?.onSuccess) {
          customOptions.onSuccess(...args)
        }
      },
      ...customOptions,
    })
  }

  // Hook: Delete entity
  function useDelete(customOptions?: UseMutationOptions<void, Error, string>) {
    const queryClient = useQueryClient()

    return useMutation<void, Error, string>({
      mutationFn: api.delete,
      onMutate: enableOptimistic
        ? async (id) => {
            // Cancel queries
            await queryClient.cancelQueries({ queryKey })

            // Snapshot previous value
            const previous = queryClient.getQueryData<TEntity[]>(queryKey)

            // Optimistically remove from list
            if (previous) {
              queryClient.setQueryData<TEntity[]>(
                queryKey,
                previous.filter((item) => (item as { id: string }).id !== id)
              )
            }

            return { previous }
          }
        : customOptions?.onMutate,
      onError: enableOptimistic
        ? (...args) => {
            // Rollback on error
            const context = { previous: queryClient.getQueryData<TEntity[]>(queryKey) }
            if (context?.previous) {
              queryClient.setQueryData(queryKey, context.previous)
            }
            if (customOptions?.onError) {
              customOptions.onError(...args)
            }
          }
        : customOptions?.onError,
      onSuccess: (...args) => {
        // Invalidate queries
        const [, id] = args
        queryClient.invalidateQueries({ queryKey })
        queryClient.invalidateQueries({ queryKey: [...queryKey, id] })

        if (customOptions?.onSuccess) {
          customOptions.onSuccess(...args)
        }
      },
      ...customOptions,
    })
  }

  return {
    useGetAll,
    useGetById,
    useCreate,
    useUpdate,
    useDelete,
  }
}
