package com.arms.egovframework.javaservice.esframework.factory.builder;

import com.arms.egovframework.javaservice.esframework.annotation.Recent;
import com.arms.egovframework.javaservice.esframework.esquery.EsQuery;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.MainGroupDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SingleValueGroupDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SubGroupFieldDTO;
import com.arms.egovframework.javaservice.esframework.model.entity.BaseEntity;
import org.opensearch.index.query.BoolQueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.search.aggregations.AbstractAggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilders;
import org.opensearch.search.sort.FieldSortBuilder;

import org.opensearch.data.client.orhlc.NativeSearchQuery;
import org.opensearch.data.client.orhlc.NativeSearchQueryBuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.arms.egovframework.javaservice.esframework.util.ReflectionUtil.fieldInfo;
import static org.opensearch.search.aggregations.BucketOrder.count;

public class AggregationQueryBuilder<T extends AbstractAggregationBuilder<T>> implements AggregationQuery<NativeSearchQuery> {

    private final List<SubGroupFieldDTO> subGroupFieldDTOS;
    private final NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    private final BoolQueryBuilder boolQuery;
    private final AbstractAggregationBuilder<T> valuesSourceAggregationBuilder;

    protected AggregationQueryBuilder(MainGroupDTO mainGroupDTO, EsQuery esQuery, AbstractAggregationBuilder<T> valuesSourceAggregationBuilder){

        this.valuesSourceAggregationBuilder = valuesSourceAggregationBuilder;
        this.subGroupFieldDTOS = mainGroupDTO.getSubGroupFieldDTOS();
        this.boolQuery = esQuery.getBoolQueryBuilder();
        this.nativeSearchQueryBuilder.withMaxResults(mainGroupDTO.isContentView() ? mainGroupDTO.getSize() : 0);
        this.nativeSearchQueryBuilder.withTrackTotalHits(true);
        this.nativeSearchQueryBuilder.withAggregations(this.valuesSourceAggregationBuilder);

        List<FieldSortBuilder> fieldSortBuilders = esQuery.getSortBuilders();

        Optional.ofNullable(boolQuery)
            .ifPresent(query-> this.nativeSearchQueryBuilder.withQuery(boolQuery));


        Optional.ofNullable(fieldSortBuilders)
            .ifPresent(sorts ->
                  nativeSearchQueryBuilder.withSorts(new ArrayList<>(fieldSortBuilders)));

    }

    protected AggregationQueryBuilder(SingleValueGroupDTO singleValueGroupDTO, EsQuery esQuery, AbstractAggregationBuilder<T> valuesSourceAggregationBuilder){
        this.valuesSourceAggregationBuilder = valuesSourceAggregationBuilder;
        this.subGroupFieldDTOS = List.of(singleValueGroupDTO.getSubGroupFieldDTO());
        int size = singleValueGroupDTO.getSize();

        boolean isViewContents = singleValueGroupDTO.isContentView();

        this.boolQuery = esQuery.getBoolQueryBuilder();
        this.nativeSearchQueryBuilder.withMaxResults(isViewContents ? size : 0);
        this.nativeSearchQueryBuilder.withTrackTotalHits(true);
        this.nativeSearchQueryBuilder.withAggregations(
                this.valuesSourceAggregationBuilder
        );
        List<FieldSortBuilder> fieldSortBuilders = esQuery.getSortBuilders();

        Optional.ofNullable(boolQuery)
                .ifPresent(query-> this.nativeSearchQueryBuilder.withQuery(boolQuery));

        Optional.ofNullable(fieldSortBuilders)
                .ifPresent(sorts ->
                        nativeSearchQueryBuilder.withSorts(new ArrayList<>(fieldSortBuilders)));
    }

    @Override
    public void applyNestedAggregation() {
        Optional.ofNullable(subGroupFieldDTOS)
                .ifPresent(subGroupFields->{
                    if(!subGroupFields.isEmpty()){
                        valuesSourceAggregationBuilder
                            .subAggregation(
                                    this.createNestedAggregation(subGroupFields)
                            );
                    }
                });
    }

    @Override
    public void applyCardinalityAggregation() {
        Optional.ofNullable(subGroupFieldDTOS)
                .ifPresent(subGroupFields->{
                    if(!subGroupFields.isEmpty()){
                        valuesSourceAggregationBuilder
                                .subAggregation(
                                        this.createCardinalityAggregation(subGroupFields)
                                );
                    }
                });
    }

    @Override
    public void applyAggregation(){
        Optional.ofNullable(subGroupFieldDTOS)
            .ifPresent(subGroupFields->{
                if(!subGroupFields.isEmpty()){
                    this.createAggregation(subGroupFields)
                            .forEach(valuesSourceAggregationBuilder::subAggregation);
                }
            });
    }

    @Override
    public void applyMetricAggregation() {
        Optional.ofNullable(subGroupFieldDTOS)
            .ifPresent(subGroupFields->{
                if(!subGroupFields.isEmpty()){
                    this.createMetricAggregation(subGroupFields)
                            .forEach(valuesSourceAggregationBuilder::subAggregation);
                }
            });
    }

    @Override
    public NativeSearchQuery create(){
        return nativeSearchQueryBuilder.build();
    }

    @Override
    public NativeSearchQuery createForRecentTrue(Class<? extends BaseEntity> entityClass) {
        String recentFieldName = fieldInfo(entityClass, Recent.class).getName();
        boolQuery.filter(QueryBuilders.termQuery(recentFieldName,true));
        return nativeSearchQueryBuilder.build();
    }


    private List<AggregationBuilder> createAggregation(List<SubGroupFieldDTO> subGroupFieldDTOS) {
        return subGroupFieldDTOS.stream()
                .map(fieldName->
                        AggregationBuilders.terms(Optional.ofNullable(fieldName.getSubFieldAlias()).orElseGet(()->"group_by_"+fieldName.getSubField()))
                                .field(fieldName.getSubField())
                                .order(count(fieldName.isAscending()))
                                .size(fieldName.getSize())).collect(Collectors.toList());
    }

    private AggregationBuilder createNestedAggregation(List<SubGroupFieldDTO> subGroupFieldDTOS) {

        return subGroupFieldDTOS.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(),
                        lst -> {
                            Collections.reverse(lst);
                            return lst;
                        }
                ))
                .stream()
                .map(fieldName ->
                        AggregationBuilders.terms(Optional.ofNullable(fieldName.getSubFieldAlias()).orElseGet(() -> "group_by_" + fieldName.getSubField()))
                                .field(fieldName.getSubField())
                                .order(count(fieldName.isAscending()))
                                .size(fieldName.getSize())
                )
                .reduce(null, (agg1, agg2) -> {
                    if (agg1 == null) {
                        return agg2;
                    } else {
                        return agg2.subAggregation(agg1);
                    }
                });
    }

    private List<AggregationBuilder> createMetricAggregation(List<SubGroupFieldDTO> subGroupFieldDTOS) {
        return subGroupFieldDTOS.stream()
                .flatMap(subGroupField -> Stream.of(
                              AggregationBuilders.avg("avg_by_" + Optional.ofNullable(subGroupField.getSubFieldAlias()).orElseGet(
                                subGroupField::getSubField)).field(subGroupField.getSubField())
                            , AggregationBuilders.max("max_by_" + Optional.ofNullable(subGroupField.getSubFieldAlias()).orElseGet(
                                subGroupField::getSubField)).field(subGroupField.getSubField())
                            , AggregationBuilders.min("min_by_" + Optional.ofNullable(subGroupField.getSubFieldAlias()).orElseGet(
                                subGroupField::getSubField)).field(subGroupField.getSubField())
                            , AggregationBuilders.sum("sum_by_" + Optional.ofNullable(subGroupField.getSubFieldAlias()).orElseGet(
                                subGroupField::getSubField)).field(subGroupField.getSubField())
                        )
                ).collect(Collectors.toList());
    }


    private AggregationBuilder createCardinalityAggregation(List<SubGroupFieldDTO> subGroupFieldDTOS) {
        return subGroupFieldDTOS.stream()
                .map(fieldName ->
                        AggregationBuilders.cardinality(Optional.ofNullable(fieldName.getSubFieldAlias()).orElseGet(() -> "group_by_" + fieldName.getSubField()))
                                .field(fieldName.getSubField())
                )
                .reduce(null, (agg1, agg2) -> agg2);
    }
    
}
