/// <summary>
        /// This will rebuild an entire index alias behind the scenes with no downtime. It creates a new index
        /// and populates it then uses Elasticsearch's hot-swapping technique to bring it online.
        /// </summary>
        public async static Task RebuildIndexWithHotSwapAsync(
            this IElasticLowLevelClient client,
            string alias,
            JObject indexMapping,
            Func <Task <IEnumerable <BulkIndexingDoc> > > reader,
            CancellationToken ctx = default(CancellationToken))
        {
            var deletableIndices = JArray.Parse((await client.CatAliasesAsync <StringResponse>(alias, new CatAliasesRequestParameters {
                Format = "json"
            }, ctx)).Body)
                                   .Select(x => new
            {
                alias = x.Value <string>("alias"),
                index = x.Value <string>("index")
            })
                                   .ToList();

            var index = GenerateUniqueIndexNameForAlias(alias);

            await client.IndicesCreateAsync <StringResponse>(index, PostData.String(indexMapping?.ToString()), ctx : ctx);

            while (!ctx.IsCancellationRequested)
            {
                // TODO: If an exception is thrown, delete the half-created index
                var docs = await reader();

                if (ctx.IsCancellationRequested || !docs.Any())
                {
                    break;
                }

                var body         = docs.SelectMany(doc => doc.ToLines().Select(x => x.ToString(Formatting.None)));
                var bulkResponse = await client.BulkAsync <StringResponse>(index, PostData.MultiJson(body));

                ThrowOnPartialBulkSuccess(bulkResponse);
            }

            if (ctx.IsCancellationRequested)
            {
                return;
            }

            var actions = deletableIndices.Select(idx => (object)new
            {
                remove = new { idx.index, idx.alias }
            }).ToList();

            actions.Add(new { add = new { index, alias } });

            // This is the hot-swap. The actions in the list are performed atomically
            await client.IndicesUpdateAliasesForAllAsync <StringResponse>(PostData.String(JObject.FromObject(new
            {
                actions
            }).ToString()), ctx : ctx);

            if (deletableIndices.Any())
            {
                await client.IndicesDeleteAsync <StringResponse>(string.Join(",", deletableIndices.Select(x => x.index)), ctx : ctx);
            }
        }
        /// <summary>
        /// This performs a bulk command against the index pointed at by an alias. This is to avoid the potential for
        /// a hotswap to change the underlying index from under us while we're reindexing. The index mapping is only
        /// used if the aliased index does not yet exist.
        /// </summary>
        public async static Task BulkTargetingAliasAsync(
            this IElasticLowLevelClient client,
            string alias,
            JObject indexMappingIfNotExists,
            Func <Task <IEnumerable <BulkIndexingDoc> > > reader,
            CancellationToken ctx = default(CancellationToken))
        {
            var targetIndices = JArray.Parse((await client.CatAliasesAsync <StringResponse>(alias, new CatAliasesRequestParameters {
                Format = "json"
            }, ctx)).Body)
                                .Select(x => x.Value <string>("index"))
                                .ToList();

            string index;

            if (targetIndices.Count == 0)
            {
                index = GenerateUniqueIndexNameForAlias(alias);

                await client.IndicesCreateAsync <StringResponse>(index, PostData.String(indexMappingIfNotExists?.ToString()), ctx : ctx);

                await client.IndicesUpdateAliasesForAllAsync <StringResponse>(PostData.String(JObject.FromObject(new
                {
                    actions = new[] { new { add = new { index, alias } } }
                }).ToString()), ctx : ctx);
            }
            else if (targetIndices.Count > 1)
            {
                throw new ArgumentException(
                          $"{nameof(BulkTargetingAliasAsync)} can only be used against an alias targeting a single index. The `{alias}` alias covers {targetIndices.Count} indices",
                          nameof(alias));
            }
            else
            {
                index = targetIndices.First();
            }

            while (!ctx.IsCancellationRequested)
            {
                var docs = await reader();

                if (ctx.IsCancellationRequested || !docs.Any())
                {
                    break;
                }

                var body         = docs.SelectMany(doc => doc.ToLines().Select(x => x.ToString(Formatting.None)));
                var bulkResponse = await client.BulkAsync <StringResponse>(index, PostData.MultiJson(body));

                ThrowOnPartialBulkSuccess(bulkResponse);
            }
        }