// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Threading.Tasks;
using Elastic.Clients.Elasticsearch.Aggregations;
using Elastic.Clients.Elasticsearch.AsyncSearch;
using Elastic.Clients.Elasticsearch.QueryDsl;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
using Tests.Core.Extensions;
using Tests.Framework.EndpointTests;
using Tests.Framework.EndpointTests.TestState;
using System;

namespace Tests.AsyncSearch
{
	public class AsyncSearchApiTests : CoordinatedIntegrationTestBase<ReadOnlyCluster>
	{
		private const string SubmitStep = nameof(SubmitStep);
		private const string StatusStep = nameof(StatusStep);
		private const string GetStep = nameof(GetStep);
		private const string DeleteStep = nameof(DeleteStep);

		public AsyncSearchApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(new CoordinatedUsage(cluster, usage)
		{
			{
				SubmitStep, u =>
				u.Calls<AsyncSearchSubmitRequestDescriptor<Project>, AsyncSearchSubmitRequest<Project>, AsyncSearchSubmitResponse<Project>>(
					v => new AsyncSearchSubmitRequest<Project>
					{
						Query = new MatchAllQuery(),
						KeepOnCompletion = true,
						WaitForCompletionTimeout = Time.MinusOne,
						Aggregations = new TermsAggregation("states")
						{
							Field = Infer.Field<Project>(p => p.State.Suffix("keyword")),
							MinDocCount = 2,
							Size = 5,
							ShardSize = 100,
							ExecutionHint = TermsAggregationExecutionHint.Map,
							Missing = "n/a",
							// TODO - Review terms agg and fix this
							//Include = new TermsInclude(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() }),
							Order = new List<TermsOrder> { TermsOrder.KeyAscending, TermsOrder.CountDescending },
							Meta = new Dictionary<string, object> { { "foo", "bar" } }
						}
					},
					(v, d) => d
						.MatchAll()
						.KeepOnCompletion()
						.WaitForCompletionTimeout(-1)
						.Aggregations(a => a
							.Terms("states", st => st
								.Field(p => p.State.Suffix("keyword"))
								.MinDocCount(2)
								.Size(5)
								.ShardSize(100)
								.ExecutionHint(TermsAggregationExecutionHint.Map)
								.Missing("n/a")
								// TODO - Review terms agg and fix this
								//.Include(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() })
								.Order(o => o
									.KeyAscending()
									.CountDescending()
								)
								.Meta(m => m
									.Add("foo", "bar")
								)
							)
						),
					(v, c, f) => c.AsyncSearch.Submit(f),
					(v, c, f) => c.AsyncSearch.SubmitAsync(f),
					(v, c, r) => c.AsyncSearch.Submit<Project>(r),
					(v, c, r) => c.AsyncSearch.SubmitAsync<Project>(r),
					onResponse: (r, values) => values.ExtendedValue("id", r.Id)
				)
			},
			{
				StatusStep, u =>
				u.Calls<AsyncSearchStatusRequestDescriptor, AsyncSearchStatusRequest, AsyncSearchStatusResponse<Project>>(
					v => new AsyncSearchStatusRequest(v),
					(v, d) => d,
					(v, c, f) => c.AsyncSearch.Status<Project>(v, f),
					(v, c, f) => c.AsyncSearch.StatusAsync<Project>(v, f),
					(v, c, r) => c.AsyncSearch.Status<Project>(r),
					(v, c, r) => c.AsyncSearch.StatusAsync<Project>(r),
					uniqueValueSelector: values => values.ExtendedValue<string>("id")
				)
			},
			{
				GetStep, u =>
				u.Calls<GetAsyncSearchRequestDescriptor, GetAsyncSearchRequest, GetAsyncSearchResponse<Project>>(
					v => new GetAsyncSearchRequest(v),
					(v, d) => d,
					(v, c, f) => c.AsyncSearch.Get<Project>(v, f),
					(v, c, f) => c.AsyncSearch.GetAsync<Project>(v, f),
					(v, c, r) => c.AsyncSearch.Get<Project>(r),
					(v, c, r) => c.AsyncSearch.GetAsync<Project>(r),
					uniqueValueSelector: values => values.ExtendedValue<string>("id")
				)
			},
			{
				DeleteStep, u =>
				u.Calls<DeleteAsyncSearchRequestDescriptor, DeleteAsyncSearchRequest, DeleteAsyncSearchResponse>(
					v => new DeleteAsyncSearchRequest(v),
					(v, d) => d,
					(v, c, f) => c.AsyncSearch.Delete(v, f),
					(v, c, f) => c.AsyncSearch.DeleteAsync(v, f),
					(v, c, r) => c.AsyncSearch.Delete(r),
					(v, c, r) => c.AsyncSearch.DeleteAsync(r),
					uniqueValueSelector: values => values.ExtendedValue<string>("id")
				)
			},
		})
		{ }

		[I]
		public async Task AsyncSearchSubmitResponse() => await Assert<AsyncSearchSubmitResponse<Project>>(SubmitStep, r =>
		{
			r.ShouldBeValid();
			r.Response.Should().NotBeNull();
			r.Response.Took.Should().BeGreaterOrEqualTo(0);
		});

		[I]
		public async Task AsyncSearchStatusResponse() => await Assert<AsyncSearchStatusResponse<Project>>(StatusStep, r =>
		{
			r.ShouldBeValid();
			r.StartTime.Should().BeOnOrBefore(DateTimeOffset.Now);
			r.ExpirationTime.Should().BeOnOrAfter(DateTimeOffset.Now);

			// TODO - Fix PR pending to make the int nullable.
			//if (r.IsRunning)
			//	r.CompletionStatus.HasValue.Should().BeFalse();
			//else
			//	r.CompletionStatus?.Should().Be(200);

			r.Shards.Total.Should().BeGreaterOrEqualTo(1);
		});

		[I]
		public async Task AsyncSearchGetResponse() => await Assert<GetAsyncSearchResponse<Project>>(GetStep, (s, r) =>
		{
			r.ShouldBeValid();
			r.Id.Should().NotBeNullOrEmpty();
			r.StartTime.Should().BeOnOrBefore(DateTimeOffset.Now);
			r.ExpirationTime.Should().BeOnOrAfter(DateTimeOffset.Now);
			r.Response.Should().NotBeNull();
			r.Response.Took.Should().BeGreaterOrEqualTo(0);
			r.Response.Hits.Should().HaveCount(10);
			var terms = r.Response.Aggregations.Terms("states");
			terms.Should().NotBeNull();
		});

		[I]
		public async Task AsyncSearchDeleteResponse() => await Assert<DeleteAsyncSearchResponse>(DeleteStep, r =>
		{
			r.ShouldBeValid();
			r.Acknowledged.Should().BeTrue();
		});
	}
}
