package com.arms.egovframework.javaservice.esframework.model.vo;

import static java.util.stream.Collectors.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Stream;

import com.arms.egovframework.javaservice.esframework.custom.client.CustomDocumentAdapters;
import lombok.Getter;
import org.opensearch.search.SearchHit;
import org.opensearch.search.aggregations.Aggregation;
import org.opensearch.search.aggregations.Aggregations;
import org.opensearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.opensearch.search.aggregations.metrics.NumericMetricsAggregation;
import org.opensearch.search.aggregations.metrics.TopHits;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.SearchDocument;


public class DocumentAggregations implements SearchDocResult {

	@Getter
	private final Long totalHits;

	@Getter
	private final Long docSumCount;

	private final List<DocumentBucket> documentBuckets;

	public DocumentAggregations(SearchHits<?> searchHits) {
		this.totalHits = searchHits.getTotalHits();

		AggregationsContainer<?> aggregations = searchHits.getAggregations();
		Map<String, Aggregation> mainBucketsAggregationMap = this.getTerm(aggregations)
				.entrySet()
				.stream()
				.collect(toMap(Entry::getKey, Entry::getValue));

		this.documentBuckets
				= new DocumentAggregations(mainBucketsAggregationMap).docBuckets();

		this.docSumCount = documentBuckets.stream().mapToLong(DocumentBucket::getDocCount).reduce(0, Long::sum);

	}

	public DocumentAggregations(List<DocumentBucket> documentBuckets) {
		this.totalHits = -1L;
		this.docSumCount = -1L;
		this.documentBuckets = documentBuckets;
	}

	DocumentAggregations(Map<String, Aggregation> bucketsAggregationMap) {

		this.documentBuckets = bucketsAggregationMap.entrySet()
				.stream()
				.flatMap(this::createDocumentBucket).collect(toList());
		this.totalHits = -1L;
		this.docSumCount = -1L;
	}

	private Stream<DocumentBucket> createDocumentBucket(Entry<String, Aggregation> bucket) {

		if(bucket.getValue() instanceof NumericMetricsAggregation.SingleValue) {
			return Stream.of((NumericMetricsAggregation.SingleValue)bucket.getValue())
					.map(bucketValue -> new DocumentBucket(bucket.getKey(), bucketValue));
		}

		// top_hits는 metric aggregation이므로 별도 처리
		if(bucket.getValue() instanceof TopHits) {
			return Stream.of((TopHits)bucket.getValue())
					.map(topHits -> new DocumentBucket(bucket.getKey(), topHits));
		}

		return ((MultiBucketsAggregation) bucket.getValue())
				.getBuckets()
				.stream().map(bucketValue -> new DocumentBucket(bucket.getKey(),bucketValue));

	}

	private Map<String, Aggregation> getTerm(AggregationsContainer<?> aggregationsContainer){

		 return Optional.ofNullable(aggregationsContainer)
				 .map(a->((Aggregations)a.aggregations()).getAsMap())
				 .orElseGet(
				HashMap::new);
	}


	public List<DocumentBucket> deepestList() {
		return documentBuckets.stream()
				.flatMap(a -> a.getDeepestDocument().stream())
				.collect(toList());
	}


	public List<DocumentBucket> docBuckets() {
		return documentBuckets;
	}

	public List<SearchHit> allSearchHits() {
		return documentBuckets.stream()
				.flatMap(bucket -> bucket.getSearchHits().stream())
				.collect(toList());
	}

	public <T> List<T> toEntities(ElasticsearchConverter converter, Class<T> clazz) {
		return allSearchHits().stream()
				.map(hit -> {
					SearchDocument searchDocument = CustomDocumentAdapters.from(hit);
					return converter.read(clazz, searchDocument);
				})
				.collect(toList());
	}

}