import {
  ClientError,
  InferClientInput,
  InferClientOutput,
} from "@mercurjs/client";
import {
  InfiniteData,
  QueryKey,
  UseInfiniteQueryOptions,
  UseMutationOptions,
  useMutation,
  useQuery,
  UseQueryOptions,
} from "@tanstack/react-query";
import { sdk, fetchQuery } from "../../lib/client";
import { queryClient } from "../../lib/query-client";
import { queryKeysFactory } from "../../lib/query-key-factory";
import { inventoryItemsQueryKeys } from "./inventory.tsx";
import { useInfiniteList } from "../use-infinite-list.tsx";

const PRODUCTS_QUERY_KEY = "products" as const;
export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY);

const VARIANTS_QUERY_KEY = "product_variants" as const;
export const variantsQueryKeys = queryKeysFactory(VARIANTS_QUERY_KEY);

const OPTIONS_QUERY_KEY = "product_options" as const;
export const optionsQueryKeys = queryKeysFactory(OPTIONS_QUERY_KEY);

export const useCreateProductOption = (
  productId: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.options.mutate>,
    ClientError,
    Omit<
      InferClientInput<typeof sdk.vendor.products.$id.options.mutate>,
      "$id"
    >
  >
) => {
  return useMutation({
    mutationFn: (payload) =>
      sdk.vendor.products.$id.options.mutate({ $id: productId, ...payload }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: optionsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });
      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useUpdateProductOption = (
  productId: string,
  optionId: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.options.$optionId.mutate>,
    ClientError,
    Omit<
      InferClientInput<typeof sdk.vendor.products.$id.options.$optionId.mutate>,
      "$id" | "$optionId"
    >
  >
) => {
  return useMutation({
    mutationFn: (payload) =>
      sdk.vendor.products.$id.options.$optionId.mutate({
        $id: productId,
        $optionId: optionId,
        ...payload,
      }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: optionsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: optionsQueryKeys.detail(optionId),
      });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useDeleteProductOption = (
  productId: string,
  optionId: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.options.$optionId.delete>,
    ClientError,
    void
  >
) => {
  return useMutation({
    mutationFn: () =>
      sdk.vendor.products.$id.options.$optionId.delete({
        $id: productId,
        $optionId: optionId,
      }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: optionsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: optionsQueryKeys.detail(optionId),
      });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useProductVariant = (
  productId: string,
  variantId: string,
  query?: Omit<
    InferClientInput<typeof sdk.vendor.products.$id.variants.$variantId>,
    "$id" | "$variantId"
  >,
  options?: UseQueryOptions<
    unknown,
    ClientError,
    InferClientOutput<typeof sdk.vendor.products.$id.variants.$variantId>
  >
) => {
  const { data, ...rest } = useQuery({
    queryFn: () =>
      sdk.vendor.products.$id.variants.$variantId.query({
        $id: productId,
        $variantId: variantId,
        ...query,
      }),
    queryKey: variantsQueryKeys.detail(variantId, query),
    ...options,
  });

  return { ...data, ...rest };
};

export const useProductVariants = (
  productId: string,
  query?: Omit<
    InferClientInput<typeof sdk.vendor.products.$id.variants.query>,
      "$id"
  >,
  options?: UseQueryOptions<
    unknown,
    ClientError,
    InferClientOutput<typeof sdk.vendor.products.$id.variants.query>
  >
) => {
  const { data, ...rest } = useQuery({
    queryFn: () => {
      if (!productId) {
        throw new Error("productId is required for useProductVariants")
      }
      return fetchQuery(`/vendor/products/${productId}/variants`, {
        method: "GET",
        query: { ...query },
      })
    },
    queryKey: variantsQueryKeys.list({ productId, ...query }),
    ...options,
    enabled: !!productId && (options?.enabled !== undefined ? options.enabled : true),
  });

  return { ...data, ...rest };
};

export const useCreateProductVariant = (
  productId: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.variants.mutate>,
    ClientError,
    Omit<
      InferClientInput<typeof sdk.vendor.products.$id.variants.mutate>,
      "$id"
    >
  >
) => {
  return useMutation({
    mutationFn: (payload) =>
      sdk.vendor.products.$id.variants.mutate({ $id: productId, ...payload }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });
      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useUpdateProductVariant = (
  productId: string,
  variantId: string,
  options?: UseMutationOptions<
    InferClientOutput<
      typeof sdk.vendor.products.$id.variants.$variantId.mutate
    >,
    ClientError,
    Omit<
      InferClientInput<
        typeof sdk.vendor.products.$id.variants.$variantId.mutate
      >,
      "$id" | "$variantId"
    >
  >
) => {
  return useMutation({
    mutationFn: (payload) =>
      sdk.vendor.products.$id.variants.$variantId.mutate({
        $id: productId,
        $variantId: variantId,
        ...payload,
      }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.detail(variantId),
      });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

type UpdateVariantMediaPayload = {
  add?: string[];
  remove?: string[];
};

export const useUpdateVariantMedia = (
  productId: string,
  variantId: string,
  options?: any
) => {
  return useMutation({
    mutationFn: (payload: UpdateVariantMediaPayload) =>
      fetchQuery(`/vendor/products/${productId}/variants/${variantId}/media`, {
        method: "POST",
        body: payload,
      }),
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.detail(variantId),
      });
      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useUpdateProductVariantsBatch = (
  productId: string,
  options?: any
) => {
  return useMutation({
    mutationFn: async (variants: Array<{ id: string; [key: string]: any }>) => {
      const promises = variants.map((variant) => {
        const { id, ...updateData } = variant
        return fetchQuery(`/vendor/products/${productId}/variants/${id}`, {
          method: "POST",
          body: updateData,
        })
      })
      return Promise.all(promises)
    },
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      })
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.lists(),
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

export const useDeleteVariant = (
  productId: string,
  variantId: string,
  options?: UseMutationOptions<
    InferClientOutput<
      typeof sdk.vendor.products.$id.variants.$variantId.delete
    >,
    ClientError,
    void
  >
) => {
  return useMutation({
    mutationFn: () =>
      sdk.vendor.products.$id.variants.$variantId.delete({
        $id: productId,
        $variantId: variantId,
      }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.detail(variantId),
      });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useDeleteVariantLazy = (
  productId: string,
  options?: UseMutationOptions<
    InferClientOutput<
      typeof sdk.vendor.products.$id.variants.$variantId.delete
    >,
    ClientError,
    { variantId: string }
  >
) => {
  return useMutation({
    mutationFn: ({ variantId }) =>
      sdk.vendor.products.$id.variants.$variantId.delete({
        $id: productId,
        $variantId: variantId,
      }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() });
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.detail(variables.variantId),
      });
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useProduct = (
  id: string,
  query?: Omit<
    InferClientInput<typeof sdk.vendor.products.$id.query>,
      "$id"
  >,
  options?: UseQueryOptions<
    unknown,
    ClientError,
    InferClientOutput<typeof sdk.vendor.products.$id.query>
  >
) => {
  const { data, ...rest } = useQuery({
    queryFn: () => sdk.vendor.products.$id.query({ $id: id, ...query }),
    queryKey: productsQueryKeys.detail(id, query),
    ...options,
  });

  return { ...data, ...rest };
};

export const useProducts = (
  query?: InferClientInput<typeof sdk.vendor.products.query>,
  options?: UseQueryOptions<
    unknown,
    ClientError,
    InferClientOutput<typeof sdk.vendor.products.query>
  >
) => {
  const { data, ...rest } = useQuery({
    queryFn: () => sdk.vendor.products.query({ ...query }),
    queryKey: productsQueryKeys.list(query),
    ...options,
  });

  return { ...data, ...rest };
};

export const useInfiniteProducts = (
  query?: Omit<
    InferClientInput<typeof sdk.vendor.products.query>,
    "offset" | "limit"
  > & {
    limit?: number;
  },
  options?: Omit<
    UseInfiniteQueryOptions<
      InferClientOutput<typeof sdk.vendor.products.query>,
      ClientError,
      InfiniteData<
        InferClientOutput<typeof sdk.vendor.products.query>,
        number
      >,
      InferClientOutput<typeof sdk.vendor.products.query>,
      QueryKey,
      number
    >,
    "queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
  >
) => {
  return useInfiniteList({
    queryKey: (params) => productsQueryKeys.list(params),
    queryFn: (params) => sdk.vendor.products.query(params),
    query,
    options,
  });
};

export const useCreateProduct = (
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.mutate>,
    ClientError,
    InferClientInput<typeof sdk.vendor.products.mutate>
  >
) => {
  return useMutation({
    mutationFn: (payload) => sdk.vendor.products.mutate(payload),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() });
      // if `manage_inventory` is true on created variants that will create inventory items automatically
      queryClient.invalidateQueries({
        queryKey: inventoryItemsQueryKeys.lists(),
      });
      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useUpdateProduct = (
  id: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.mutate>,
    ClientError,
    Omit<InferClientInput<typeof sdk.vendor.products.$id.mutate>, "$id">
  >
) => {
  return useMutation({
    mutationFn: (payload) =>
      sdk.vendor.products.$id.mutate({ $id: id, ...payload }),
    onSuccess: async (data, variables, context) => {
      await queryClient.invalidateQueries({
        queryKey: productsQueryKeys.lists(),
      });
      await queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(id),
      });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

export const useDeleteProduct = (
  id: string,
  options?: UseMutationOptions<
    InferClientOutput<typeof sdk.vendor.products.$id.delete>,
    ClientError,
    void
  >
) => {
  return useMutation({
    mutationFn: () => sdk.vendor.products.$id.delete({ $id: id }),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() });
      queryClient.invalidateQueries({ queryKey: productsQueryKeys.detail(id) });

      options?.onSuccess?.(data, variables, context);
    },
    ...options,
  });
};

type ProductAttributesResponse = {
  attributes: any[]
}

const productAttributesQueryKey = (id: string) => [
  "product-attributes",
  id,
]

export const useProductAttributes = (_id: string) => {
  const { data, ...rest } = useQuery<ProductAttributesResponse>({
    queryFn: () =>
      sdk.vendor.attributes.query({
        fields: "id,name,handle,description,ui_component,is_required,possible_values.*,product_categories.*",
        limit: 100,
      } as any),
    queryKey: productAttributesQueryKey(_id),
  })

  return { ...data, ...rest }
}

/* Attribute Management Hooks */

type AddProductAttributePayload = {
  name?: string
  values: string[]
  use_for_variations: boolean
  ui_component?: string
  attribute_id?: string
}

export const useAddProductAttribute = (
  productId: string,
  options?: UseMutationOptions<any, ClientError, AddProductAttributePayload>
) => {
  return useMutation({
    mutationFn: (payload: AddProductAttributePayload) =>
      fetchQuery(`/vendor/products/${productId}/attributes`, {
        method: "POST",
        body: payload,
      }),
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      })
      queryClient.invalidateQueries({
        queryKey: productAttributesQueryKey(productId),
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

type UpdateProductAttributePayload = {
  values?: string[]
  use_for_variations?: boolean
  name?: string
  ui_component?: string
}

export const useUpdateProductAttribute = (
  productId: string,
  attributeId: string,
  options?: UseMutationOptions<
    any,
    ClientError,
    UpdateProductAttributePayload
  >
) => {
  return useMutation({
    mutationFn: (payload: UpdateProductAttributePayload) =>
      fetchQuery(
        `/vendor/products/${productId}/attributes/${attributeId}`,
        {
          method: "POST",
          body: payload,
        }
      ),
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      })
      queryClient.invalidateQueries({
        queryKey: productAttributesQueryKey(productId),
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

type RemoveProductAttributeResponse = {
  product_id: string
  attribute_id: string
  deleted: boolean
  removed_values_count: number
}

export const useRemoveProductAttribute = (
  productId: string,
  attributeId: string,
  options?: UseMutationOptions<
    RemoveProductAttributeResponse,
    ClientError,
    void
  >
) => {
  return useMutation({
    mutationFn: () =>
      fetchQuery(
        `/vendor/products/${productId}/attributes/${attributeId}`,
        {
          method: "DELETE",
        }
      ),
    onSuccess: (data, variables, context) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      })
      queryClient.invalidateQueries({
        queryKey: productAttributesQueryKey(productId),
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

/* Variant Media Hook */

type BatchVariantImagesPayload = {
  add?: string[]
  remove?: string[]
}

type BatchVariantImagesResponse = {
  added: number
  removed: number
}

export const useBatchVariantImages = (
  productId: string,
  variantId: string,
  options?: UseMutationOptions<
    BatchVariantImagesResponse,
    ClientError,
    BatchVariantImagesPayload
  >
) => {
  return useMutation({
    mutationFn: (payload: BatchVariantImagesPayload) =>
      fetchQuery(
        `/vendor/products/${productId}/variants/${variantId}/media`,
        {
          method: "POST",
          body: payload,
        }
      ),
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: variantsQueryKeys.detail(variantId),
      })
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.detail(productId),
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

/* Batch Update Products */

export type BatchUpdateProductItem = {
  id: string
  title?: string
  status?: "draft" | "published"
  discountable?: boolean
}

export type BatchUpdateProductsPayload = {
  update: BatchUpdateProductItem[]
  delete?: string[]
}

export const useBatchUpdateProducts = (
  options?: UseMutationOptions<void, ClientError, BatchUpdateProductsPayload>
) => {
  return useMutation({
    mutationFn: async (payload: BatchUpdateProductsPayload) => {
      return fetchQuery(`/vendor/products/batch`, {
        method: "POST",
        body: payload,
      })
    },
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.lists(),
      })
      variables.update?.forEach((item: BatchUpdateProductItem) => {
        queryClient.invalidateQueries({
          queryKey: productsQueryKeys.detail(item.id),
        })
      })
      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}

export const useBulkDeleteProducts = (
  options?: UseMutationOptions<any[], ClientError, string[]>
) => {
  return useMutation({
    mutationFn: async (productIds: string[]) => {
      const deletePromises = productIds.map((id) =>
        fetchQuery(`/vendor/products/${id}`, {
          method: "DELETE",
        })
      )
      return Promise.all(deletePromises)
    },
    onSuccess: (data: any, variables: any, context: any) => {
      queryClient.invalidateQueries({
        queryKey: productsQueryKeys.lists(),
      })

      variables.forEach((id: string) => {
        queryClient.invalidateQueries({
          queryKey: productsQueryKeys.detail(id),
        })
      })

      options?.onSuccess?.(data, variables, context)
    },
    ...options,
  })
}
