import { useMutation, useQueryClient, QueryKey, UseMutationOptions } from '@tanstack/react-query';
import apiClient from '../api/client';
import { AxiosError, AxiosResponse } from 'axios';

type HttpMethod = 'POST' | 'PUT' | 'PATCH' | 'DELETE';

interface CrudMutationConfig<TData, TVariables, TResponse = TData> {
  /** The API endpoint (e.g., '/resources') */
  endpoint: string;
  /** HTTP method */
  method: HttpMethod;
  /** Query keys to invalidate on success */
  invalidateKeys?: QueryKey[];
  /** Transform response data */
  transformResponse?: (response: AxiosResponse<TResponse>) => TData;
  /** React Query mutation options */
  options?: Omit<
    UseMutationOptions<TData, AxiosError, TVariables>,
    'mutationFn'
  >;
}

/**
 * Generic CRUD mutation hook factory
 *
 * @example
 * // Create a resource
 * const useCreateResource = () => useCrudMutation<Resource, CreateResourceData>({
 *   endpoint: '/resources',
 *   method: 'POST',
 *   invalidateKeys: [['resources']],
 * });
 *
 * @example
 * // Update a resource
 * const useUpdateResource = () => useCrudMutation<Resource, { id: string; data: Partial<Resource> }>({
 *   endpoint: '/resources',
 *   method: 'PATCH',
 *   invalidateKeys: [['resources']],
 * });
 */
export function useCrudMutation<TData = unknown, TVariables = unknown, TResponse = TData>({
  endpoint,
  method,
  invalidateKeys = [],
  transformResponse,
  options = {},
}: CrudMutationConfig<TData, TVariables, TResponse>) {
  const queryClient = useQueryClient();

  return useMutation<TData, AxiosError, TVariables>({
    mutationFn: async (variables: TVariables) => {
      let response: AxiosResponse<TResponse>;

      // Handle different variable shapes for different methods
      if (method === 'DELETE') {
        // For DELETE, variables is typically just the ID
        const id = typeof variables === 'object' && variables !== null && 'id' in variables
          ? (variables as { id: string | number }).id
          : variables;
        response = await apiClient.delete(`${endpoint}/${id}/`);
      } else if (method === 'PUT' || method === 'PATCH') {
        // For PUT/PATCH, variables should have id and data
        if (typeof variables === 'object' && variables !== null && 'id' in variables) {
          const { id, ...data } = variables as { id: string | number; [key: string]: unknown };
          response = await apiClient[method.toLowerCase() as 'put' | 'patch'](`${endpoint}/${id}/`, data);
        } else {
          // If no id, just send to the endpoint
          response = await apiClient[method.toLowerCase() as 'put' | 'patch'](`${endpoint}/`, variables);
        }
      } else {
        // POST - create new
        response = await apiClient.post(`${endpoint}/`, variables);
      }

      return transformResponse ? transformResponse(response) : (response.data as unknown as TData);
    },
    onSuccess: (data, variables, context) => {
      // Invalidate specified query keys
      invalidateKeys.forEach((key) => {
        queryClient.invalidateQueries({ queryKey: key });
      });

      // Call custom onSuccess if provided
      options.onSuccess?.(data, variables, context);
    },
    ...options,
  });
}

/**
 * Create hook factory for a resource
 */
export function createCrudHooks<
  TResource,
  TCreateData = Partial<TResource>,
  TUpdateData = Partial<TResource>
>(endpoint: string, queryKey: string) {
  return {
    useCreate: (options?: UseMutationOptions<TResource, AxiosError, TCreateData>) =>
      useCrudMutation<TResource, TCreateData>({
        endpoint,
        method: 'POST',
        invalidateKeys: [[queryKey]],
        options,
      }),

    useUpdate: (options?: UseMutationOptions<TResource, AxiosError, { id: string | number } & TUpdateData>) =>
      useCrudMutation<TResource, { id: string | number } & TUpdateData>({
        endpoint,
        method: 'PATCH',
        invalidateKeys: [[queryKey]],
        options,
      }),

    useDelete: (options?: UseMutationOptions<void, AxiosError, string | number>) =>
      useCrudMutation<void, string | number>({
        endpoint,
        method: 'DELETE',
        invalidateKeys: [[queryKey]],
        options: options as UseMutationOptions<void, AxiosError, string | number>,
      }),
  };
}

export default useCrudMutation;
