using Bp.Sync.Domain.Elastics.BaseEntities;
using Bp.Sync.Dto.Models;
using Bp.Sync.Dto.SmartTable;
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Bp.Sync.BackEnd.Infrastructure.ElasticDbContext
{
    public class ElasticIndex<T> where T : ElasticBaseEntityWithKey<long>
    {
        private readonly ElasticClient _elasticClient;

        public ElasticIndex(string clusterUrl, string indexName)
        {
            var settings = new ConnectionSettings(new Uri(clusterUrl)).DefaultIndex(indexName.ToLower());
            _elasticClient = new ElasticClient(settings);
            if (!_elasticClient.IndexDocument(indexName.ToLower()).IsValid)
            {
                _elasticClient.Indices.Create(indexName.ToLower(), c => c.Map<T>(m => m.AutoMap()));
            }
        }

        public async Task<IQueryable<T>> GetByIds(List<long> ids)
        {
            try
            {
                var Bill = await _elasticClient.SearchAsync<T>(x => x
                    .Size(10000)
                    .Query(q => q.Terms(t => t.Field(f => f.Id).Terms(ids))));
                return Bill.Documents.AsQueryable();
            }
            catch (Exception ex)
            {
                var a = ex.Message;
                return null;
            }
        }

        public async Task<T> GetById(long id)
        {
            var Bill = await _elasticClient.GetAsync<T>(id);
            return Bill.Source;
        }

        public async Task<IndexResponse> Insert(T model)
        {
            return await _elasticClient.IndexDocumentAsync(model);
        }

        public async Task<BulkResponse> InsertMany(IEnumerable<T> models)
        {
            var rs = await _elasticClient.IndexManyAsync(models);
            return rs;
        }

        public async Task<BulkResponse> DeleteMany(IEnumerable<T> models)
        {
            var rs = await _elasticClient.DeleteManyAsync(models);
            return rs;
        }

        public async Task<DeleteByQueryResponse> DeleteByIds(IEnumerable<long> ids)
        {
            var rs = await _elasticClient.DeleteByQueryAsync<T>(q => q.Query(qq => qq.Terms(t => t.Field(f => f.Id).Terms(ids))));
            return rs;
        }

        public async Task<SmartTableResult<T>> Search(BoolQueryDescriptor<T> query)
        {
            var orderSearch = await _elasticClient.SearchAsync<T>(x => x
                .Size(10000)
                .Query(q => q.Bool(b => query))
                .Scroll("1000s"));

            List<T> results = new List<T>();
            if (!orderSearch.IsValid || string.IsNullOrEmpty(orderSearch.ScrollId))
                return new SmartTableResult<T>();
            if (orderSearch.Documents.Any())
                results.AddRange(orderSearch.Documents);
            string scrollid = orderSearch.ScrollId;
            bool isScrollSetHasData = true;
            while (isScrollSetHasData)
            {
                var loopingResponse = _elasticClient.Scroll<T>("60s", scrollid);
                if (loopingResponse.IsValid)
                {
                    results.AddRange(loopingResponse.Documents);
                    scrollid = loopingResponse.ScrollId;
                }
                isScrollSetHasData = loopingResponse.Documents.Count >= 10000;
            }
            _elasticClient.ClearScroll(new ClearScrollRequest(scrollid));
            var result = new SmartTableResult<T>()
            {
                Items = results,
            };
            return result;
        }

        public async Task<SmartTableResult<T>> Search(BoolQueryDescriptor<T> query, int count)
        {
            var orderSearch = await _elasticClient.SearchAsync<T>(x => x
                .Sort(s => s.Descending(d => d.Id))
                .Size(count)
                .Query(q => q.Bool(b => query)));
            var result = new SmartTableResult<T>()
            {
                Items = orderSearch.Documents,
            };
            return result;
        }

        public async Task<ISearchResponse<T>> SearchAgg(BoolQueryDescriptor<T> query, AggregationContainerDescriptor<T> aggs)
        {
            var result = await _elasticClient.SearchAsync<T>(x => x
                .RequestCache(true)
                .Size(0)
                .Query(q => q.Bool(b => query))
                .Aggregations(a => aggs)
            );
            return result;
        }

        public async Task<SmartTableResult<BaseLong>> SearchIds(BoolQueryDescriptor<T> query, int count)
        {
            var orderSearch = await _elasticClient.SearchAsync<BaseLong>(x => x
                .Sort(s => s.Descending(d => d.Id))
                .Size(count)
                .Query(q => q.Bool(b => query)));
            var result = new SmartTableResult<BaseLong>()
            {
                Items = orderSearch.Documents,
            };
            return result;
        }

        public async Task<SmartTableResult<BaseLong>> SearchIds(BoolQueryDescriptor<T> query)
        {
            var orderSearch = await _elasticClient.SearchAsync<BaseLong>(x => x
                .Size(10000)
                .Query(q => q.Bool(b => query))
                .Scroll("1000s"));

            List<BaseLong> results = new List<BaseLong>();
            if (!orderSearch.IsValid || string.IsNullOrEmpty(orderSearch.ScrollId))
                return new SmartTableResult<BaseLong>();
            if (orderSearch.Documents.Any())
                results.AddRange(orderSearch.Documents);
            string scrollid = orderSearch.ScrollId;
            bool isScrollSetHasData = true;
            while (isScrollSetHasData)
            {
                var loopingResponse = _elasticClient.Scroll<BaseLong>("60s", scrollid);
                if (loopingResponse.IsValid)
                {
                    results.AddRange(loopingResponse.Documents);
                    scrollid = loopingResponse.ScrollId;
                }
                isScrollSetHasData = loopingResponse.Documents.Count >= 10000;
            }
            _elasticClient.ClearScroll(new ClearScrollRequest(scrollid));
            var result = new SmartTableResult<BaseLong>()
            {
                Items = results,
            };
            return result;
        }

        public async Task<SmartTableResult<T>> SearchBySort(BoolQueryDescriptor<T> query, SortDescriptor<T> searchDescriptor, int count)
        {
            var orderSearch = await _elasticClient.SearchAsync<T>(x => x
                .Sort(s => searchDescriptor)
                .Size(count)
                .Query(q => q.Bool(b => query))
                );
            var result = new SmartTableResult<T>()
            {
                Items = orderSearch.Documents ?? new List<T>(),
            };
            return result;
        }
    }
}