package com.arms.egovframework.javaservice.esframework.repository.common;

import com.arms.egovframework.javaservice.esframework.annotation.*;
import com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery;
import com.arms.egovframework.javaservice.esframework.factory.SearchDocQueryFactory;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.CompositeGroupDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SearchDocDTO;
import com.arms.egovframework.javaservice.esframework.model.vo.CatIndexVO;
import com.arms.egovframework.javaservice.esframework.model.vo.CompositeAggregationResultWrapper;
import com.arms.egovframework.javaservice.esframework.model.vo.DocumentAggregations;
import com.arms.egovframework.javaservice.esframework.model.vo.DocumentResultWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.EntityUtils;
import org.opensearch.client.Request;
import org.opensearch.client.RequestOptions;
import org.opensearch.client.Response;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.common.unit.TimeValue;
import org.opensearch.data.client.orhlc.NativeSearchQuery;
import org.opensearch.data.client.orhlc.NativeSearchQueryBuilder;
import org.opensearch.data.client.orhlc.OpenSearchRestTemplate;
import org.opensearch.data.core.OpenSearchOperations;
import org.opensearch.index.query.BoolQueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.query.TermsQueryBuilder;
import org.opensearch.index.reindex.ReindexRequest;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.repository.support.ElasticsearchEntityInformation;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

import static com.arms.api.util.ApplicationContextProvider.getBean;
import static com.arms.egovframework.javaservice.esframework.util.ReflectionUtil.*;
import static java.util.stream.Collectors.*;

/*
	ApplicationContextAware 를 사용할때에는 순환참조를 사용 하는게 아닌지 반드시 확인해야한다.
*/
@Slf4j
public class EsCommonRepositoryImpl<T, U extends Serializable> extends SimpleElasticsearchWrapperRepository<T, U> {

    private final SimpleDateFormat defaultFormater;

    public EsCommonRepositoryImpl(ElasticsearchEntityInformation<T, U> metadata
            , OpenSearchOperations openSearchOperations) {

        super(metadata, openSearchOperations);

        this.defaultFormater = new SimpleDateFormat(this.findDateFormat(rollingIndexNameSuffix()));

    }

    @Override
    public DocumentAggregations aggregateDocs(Query query) {
        return new DocumentAggregations(operations.search(query, entityClass));
    }

    @Override
    public CompositeAggregationResultWrapper<T> compositeHits(Query query) {
        SearchHits<T> searchHits = operations.search(query, entityClass);
        return new CompositeAggregationResultWrapper<>(searchHits, operations.getElasticsearchConverter(), entityClass);
    }

    @Override
    public CompositeAggregationResultWrapper<T> compositeAllHits(CompositeGroupDTO compositeGroupDTO, Function<CompositeGroupDTO,Query> function) {

        List<T> allResults = new ArrayList<>();

        while (true) {
            Query query = function.apply(compositeGroupDTO);
            CompositeAggregationResultWrapper<T> result = compositeHits(query);

            List<T> docs = result.toDocs();
            if (docs.isEmpty()) {
                break;
            }

            allResults.addAll(docs);

            Map<String, Object> afterKey = result.getAfterKey();
            if (afterKey == null) {
                break;
            }

            compositeGroupDTO.setAfterKey(afterKey);
        }

        return new CompositeAggregationResultWrapper<>(allResults, entityClass);
    }

    @Override
    public DocumentResultWrapper<T> findAllHits() {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withMaxResults(10000);
        return new DocumentResultWrapper<>(operations.search(nativeSearchQueryBuilder.build(), entityClass),
                entityClass);
    }

    @Override
    public DocumentResultWrapper<T> findHits(Query query) {
        return new DocumentResultWrapper<>(operations.search(query, entityClass), entityClass);
    }

    @Override
    public DocumentResultWrapper<T> findRecentAllHits() {

        String recentFieldName = Optional.of(fieldInfo(entityClass, Recent.class).getAnnotation(Field.class).name())
            .filter(s->!s.isEmpty())
            .orElse(fieldInfo(entityClass, Recent.class).getName());

        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(recentFieldName, true);

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(termsQueryBuilder);

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        nativeSearchQueryBuilder.withMaxResults(10000);

        return new DocumentResultWrapper<>(operations.search(nativeSearchQueryBuilder.build(), entityClass),
                entityClass);
    }

    @Override
    public DocumentResultWrapper<T> findRecentHits(Query query) {

        try{
            return new DocumentResultWrapper<>(
                    operations.search(query, entityClass), entityClass);
        } catch (NoSuchIndexException e) {
            String errorMessage = e.getMessage();
            if (errorMessage != null && errorMessage.contains("no such index")) {
                throw new IllegalArgumentException(e.getMessage());
            }
            throw e;
        } catch (Exception e){
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    @Override
    public T findRecentDocByRecentId(U u) {

        try{

            String recentIdFieldName = Optional.of(fieldInfo(entityClass, RecentId.class).getAnnotation(Field.class).name())
                .filter(s->!s.isEmpty())
                .orElse(fieldInfo(entityClass, RecentId.class).getName());

            String recentFieldName = Optional.of(fieldInfo(entityClass, Recent.class).getAnnotation(Field.class).name())
                .filter(s->!s.isEmpty())
                .orElse(fieldInfo(entityClass, Recent.class).getName());

            SearchDocDTO searchDocDTO = SimpleQuery.termQueryFilter(recentIdFieldName, u).andTermQueryFilter(recentFieldName, true).toSearchDoc();

            DocumentResultWrapper<T> documentSearchResult
                    = new DocumentResultWrapper<>(operations.search(
                SearchDocQueryFactory.searchDoc(searchDocDTO).create(), entityClass), entityClass);
            return documentSearchResult.fetchOnlyOne();

        } catch (NoSuchIndexException e) {
            String errorMessage = e.getMessage();
            if (errorMessage != null && errorMessage.contains("no such index")) {
                throw new IllegalArgumentException(e.getMessage());
            }
            throw e;
        } catch (Exception e){
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    @Override
    public T findDocById(U u) {
        return operations.get(String.valueOf(u), entityClass);
    }

    @Override
    public DocumentResultWrapper<T> findRecentDocById(U u) {
        Query query = operations.idsQuery(List.of(String.valueOf(u)));
        return new DocumentResultWrapper<>(operations.search(query, entityClass),entityClass);
    }

    @Override
    public <S extends T> S saveEmpty(S entity){
        return operations.save(entity, rollingIndexName());
    }

    @Override
    public <S extends T> Iterable<S> saveAll(Iterable<S> entities){
        ElasticSearchIndex annotation = AnnotationUtils.findAnnotation(entityClass, ElasticSearchIndex.class);
        if(annotation==null){
            return operations.save(entities);
        }
        return this.saveSupport(entities);

    }

    @Override
    public <S extends T> S modifyWithIndexName(S entity, String indexName){
        return operations.save(entity, IndexCoordinates.of(indexName));
    }

    @Override
    public <S extends T> S  save(S entity){

        ElasticSearchIndex elasticSearchIndexAnnotation = AnnotationUtils.findAnnotation(entityClass, ElasticSearchIndex.class);

        if(isExistFieldWithAnnotation(entityClass, ElasticSearchCreatedDate.class)){

            String createDateName = fieldInfo(entityClass, ElasticSearchCreatedDate.class).getName();

            Date value = (Date) getValue(entity, createDateName);

            if(value==null){
                setValue(entity, createDateName, generateDate(new Date()));
            }

        }

        if(isExistFieldWithAnnotation(entityClass, ElasticSearchUpdateDate.class)){

            String updateDateName = fieldInfo(entityClass, ElasticSearchUpdateDate.class).getName();
            setValue(entity, updateDateName, generateDate(new Date()));

        }

        if(elasticSearchIndexAnnotation==null){
            return operations.save(entity);
        }

        if(isExistFieldWithAnnotation(entityClass, Id.class)){
            String idName = fieldInfo(entityClass, Id.class).getName();
            setValue(entity, idName, null);//아이디 초기화 하여 중복된 아이디 발생 방지
        }

        Iterable<S> s = this.saveSupport(List.of(entity));

        if(!s.iterator().hasNext()){
            log.info("저장할 데이터가 없습니다.");
        }

        return entity;
    }

    private Date generateDate(Date date) {
        // 타임스탬프를 UTC+9로 변환
        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC+9"));
        ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("UTC+9"));
        return Date.from(zonedDateTime.toInstant());
    }

    private  <S extends T> Iterable<S> saveSupport(Iterable<S> entities) {

        log.info("[ 공통저장소_구현체 :: saveSupport ] entities = {}",entities);

        RecentFieldConvertor<S,T> recentFieldConvertor;

        try {
            recentFieldConvertor = new RecentFieldConvertor<>(entities,entityClass,operations);
        } catch (Exception e) {
            log.info(e.getMessage());
            return entities;
        }

        Map<String, List<T>> recentFalseListIfNotEqual
            = recentFieldConvertor.listMapGroupByIndex(recentFieldConvertor::setRecentFalseIfNotEqual);

        saveRecentFalseList(recentFalseListIfNotEqual);

        Map<String, List<T>> recentFalseListIfDuplicate
            = recentFieldConvertor.listMapGroupByIndex(recentFieldConvertor::setRecentFalseIfDuplicate);

        saveRecentFalseList(recentFalseListIfDuplicate);

        List<S> recentTrueList = StreamSupport.stream(entities.spliterator(), false)
                .filter(Objects::nonNull)
                .map(recentFieldConvertor::recentTrue)
                .filter(Objects::nonNull)
            .toList();

        return recentTrueList.stream()
                    .map(doc-> operations.save(doc, rollingIndexName()))
                    .collect(toList());

    }

    private <S> void saveRecentFalseList( Map<String, List<S>> recentFalseListMap) {
        recentFalseListMap
            .forEach((key, values) -> values.forEach(
                value-> operations.save(value, IndexCoordinates.of(key))
            ));
    }


    @Override
    public List<T> findRecentDocsByScrollApi(Query query) {

        OpenSearchRestTemplate template = (OpenSearchRestTemplate) operations;
        IndexCoordinates indexCoordinatesFor = template.getIndexCoordinatesFor(entityClass);

        SearchScrollHits<T> scroll
            = template.searchScrollStart(60000, query, entityClass, indexCoordinatesFor);

        String scrollId = scroll.getScrollId();
        List<T> entities = new ArrayList<>();

        while (scroll.hasSearchHits()) {
            entities.addAll(
                scroll.getSearchHits()
                    .stream()
                    .map(SearchHit::getContent)
                    .toList()
            );
            scrollId = scroll.getScrollId();
            scroll = template.searchScrollContinue(scrollId, 60000, entityClass,indexCoordinatesFor);
        }

        assert scrollId != null;
        template.searchScrollClear(List.of(scrollId));

        return entities;

    }

    @Override
    public List<T> findAllDocsBySearchAfter(SearchDocDTO searchDocDTO, Function<SearchDocDTO, Query> function) {

        List<T> allResults = new ArrayList<>();

        while (true) {
            Query query = function.apply(searchDocDTO);
            DocumentResultWrapper<T> result = findHits(query);

            List<T> docs = result.toDocs();
            if (docs.isEmpty()) {
                break;
            }

            allResults.addAll(docs);

            List<Object> lastSortValue = result.getLastSortValue();
            if (lastSortValue == null || lastSortValue.isEmpty()) {
                break;
            }

            searchDocDTO.setSearchAfter(lastSortValue);
        }

        return allResults;
    }

    @Override
    public List<T> findAllRecentDocsBySearchAfter(SearchDocDTO searchDocDTO, Function<SearchDocDTO, Query> function) {

        List<T> allResults = new ArrayList<>();

        while (true) {
            Query query = function.apply(searchDocDTO);
            DocumentResultWrapper<T> result = findRecentHits(query);

            List<T> docs = result.toDocs();
            if (docs.isEmpty()) {
                break;
            }

            allResults.addAll(docs);

            List<Object> lastSortValue = result.getLastSortValue();
            if (lastSortValue == null || lastSortValue.isEmpty()) {
                break;
            }

            searchDocDTO.setSearchAfter(lastSortValue);
        }

        return allResults;
    }

    @Override
    public List<T> findDocsByScrollApi(Query query) {

        NativeSearchQuery nativeSearchQuery = (NativeSearchQuery)query;

        OpenSearchRestTemplate template = (OpenSearchRestTemplate) operations;

        IndexCoordinates indexCoordinatesFor = template.getIndexCoordinatesFor(entityClass);

        SearchScrollHits<T> scroll
                = template.searchScrollStart(60000, nativeSearchQuery, entityClass, indexCoordinatesFor);

        String scrollId = scroll.getScrollId();
        List<T> entities = new ArrayList<>();

        while (scroll.hasSearchHits()) {
            entities.addAll(
                    scroll.getSearchHits()
                            .stream()
                            .map(SearchHit::getContent)
                            .toList()
            );
            scrollId = scroll.getScrollId();
            scroll = template.searchScrollContinue(scrollId, 60000, entityClass,indexCoordinatesFor);
        }

        assert scrollId != null;
        template.searchScrollClear(List.of(scrollId));

        return entities;

    }

    @Override
    public String indexAliasName() {
        IndexCoordinates indexCoordinatesFor = operations.getIndexCoordinatesFor(entityClass);
        return indexCoordinatesFor.getIndexName();
    }

    @Override
    public void deleteIndex(int day){
        this.indexWithDayRange(day)
            .forEach(indexName-> {
                    log.info("delete-index->{}",indexName);
                    this.deleteIndexByMonth(IndexCoordinates.of(indexName));
                }
            );
    }

    @Override
    public String deleteDocByEntity(T t) {
        return operations.delete(t);
    }

    @Override
    public U deleteRecentDocById(U u) {
        this.deleteAllById(List.of(u));
        return u;
    }

    private IndexCoordinates rollingIndexName() {
        Document document = AnnotationUtils.findAnnotation(entityClass, Document.class);
        if(document!=null){

            IndexCoordinates indexCoordinatesFor = operations.getIndexCoordinatesFor(entityClass);

            String indexName = indexCoordinatesFor.getIndexName();

            try{

                Method method = methodInfo(entityClass, RollingIndexName.class);

                if(method!=null){
                    Constructor<T> constructor = entityClass.getConstructor();
                    T t = constructor.newInstance();
                    return IndexCoordinates.of(indexName +"-"+method.invoke(t));

                }else{
                    return IndexCoordinates.of(indexName);
                }
            }catch (Exception e) {
                return IndexCoordinates.of(indexName);
            }
        }
        throw new IllegalArgumentException("인덱스명을 확인해주시길 바랍니다.");
    }

    private String rollingIndexNameSuffix() {
        String indexName = this.rollingIndexName().getIndexName();
        return Optional.of(indexName.split("-", 2))
                .filter(a -> a.length > 1)
                .map(a -> a[1]).orElse("0000-00-00");
    }


    @Override
    public void merge(int day) {

        Map<String, List<String>> indexNameGroupByMonth = indexWithDayRange(day)
                .stream()
                .collect(groupingBy(str -> str.substring(0, str.length() - 2) + "00")).entrySet().stream()
                .collect(toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue().stream().toList()
                ));
        reindex(indexNameGroupByMonth);
        deleteIndex(indexNameGroupByMonth);
    }

    @Override
    public void reindex(Map<String, List<String>> indexNameGroupByMonth) {
        ReindexRequest reindexRequest = new ReindexRequest();
        reindexRequest.setTimeout(TimeValue.MAX_VALUE);
        reindexRequest.setRefresh(true);

        log.info("reindex 시작");
        indexNameGroupByMonth.forEach((destIndex, sourceIndices) -> {
            // 리인덱스 전 소스 인덱스들의 총 문서 수 계산
            long sourceDocsCountBefore = sourceIndices.stream()
                    .mapToLong(this::getIndexDocsCount)
                    .sum();
            long destDocsCountBefore = getIndexDocsCount(destIndex);
            long expectedTotalCount = sourceDocsCountBefore + destDocsCountBefore;

            log.info("[검증] 리인덱스 전 - 소스 인덱스들 총 문서 수: {}, 목적지 인덱스({}) 문서 수: {}, 예상 총합: {}",
                    sourceDocsCountBefore, destIndex, destDocsCountBefore, expectedTotalCount);

            for (String docIndexName : sourceIndices) {
                try {
                    long indexDocCount = getIndexDocsCount(docIndexName);
                    log.info("[검증] 소스 인덱스 {} 문서 수: {}", docIndexName, indexDocCount);

                    reindexRequest.setSourceIndices(docIndexName);
                    reindexRequest.setDestIndex(destIndex);
                    reindexRequest(reindexRequest, 0);

                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
            }

            // 리인덱스 후 목적지 인덱스의 문서 수 확인
            long destDocsCountAfter = getIndexDocsCount(destIndex);
            log.info("[검증] 리인덱스 후 - 목적지 인덱스({}) 문서 수: {}", destIndex, destDocsCountAfter);

            // 검증 결과 출력
            if (destDocsCountAfter == expectedTotalCount) {
                log.info("[검증 성공] 문서 수 일치 - 예상: {}, 실제: {}", expectedTotalCount, destDocsCountAfter);
            } else {
                String errorMessage = String.format(
                        "[검증 실패] 리인덱스 문서 수 불일치 - 목적지 인덱스: %s, 예상: %d, 실제: %d, 차이: %d",
                        destIndex, expectedTotalCount, destDocsCountAfter, expectedTotalCount - destDocsCountAfter);
                log.error(errorMessage);
                throw new IllegalStateException(errorMessage);
            }
        });
        log.info("reindex 종료");
    }

    private long getIndexDocsCount(String indexName) {
        try {
            RestHighLevelClient restHighLevelClient = getBean(RestHighLevelClient.class);
            ObjectMapper objectMapper = new ObjectMapper();
            Response response = restHighLevelClient.getLowLevelClient().performRequest(
                    new Request("GET", "/_cat/indices/" + indexName + "?format=json")
            );
            String responseBody = EntityUtils.toString(response.getEntity());
            List<CatIndexVO> catIndexVOList = objectMapper.readValue(responseBody, new TypeReference<>() {});
            return catIndexVOList.stream()
                    .mapToLong(vo -> vo.getDocsCount() != null ? vo.getDocsCount() : 0L)
                    .sum();
        } catch (Exception e) {
            log.debug("인덱스 {} 문서 수 조회 실패 (인덱스가 없을 수 있음): {}", indexName, e.getMessage());
            return 0L;
        }
    }

    @Override
    public void deleteIndex(Map<String, List<String>> indexNameGroupByMonth) {
        log.info("reindex 이후 delete 시작");
        indexNameGroupByMonth.forEach((key, value) -> {
           for (String docIndexName : value) {
               try {
                   this.deleteIndexByMonth(IndexCoordinates.of(docIndexName));
               } catch (Exception e) {
                   throw new IllegalArgumentException(e);
               }
           }
        });
        log.info("reindex 이후 delete 종료");
    }

    private void reindexRequest(ReindexRequest reindexRequest, int count){
        try {
            RestHighLevelClient restHighLevelClient = getBean(RestHighLevelClient.class);

            // 비동기 리인덱스 실행 (wait_for_completion=false)
            String sourceIndex = reindexRequest.getSearchRequest().indices()[0];
            String destIndex = reindexRequest.getDestination().index();
            String taskId = submitReindexTask(restHighLevelClient, sourceIndex, destIndex);
            log.info("리인덱스 태스크 시작 - taskId: {}, source: {}, dest: {}", taskId, sourceIndex, destIndex);

            // 태스크 완료 대기 및 결과 확인
            Map<String, Object> taskResult = waitForTaskCompletion(restHighLevelClient, taskId);
            logReindexResult(taskId, taskResult);

        } catch (Exception e) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
            if(count>3){
                log.warn("인덱스 머지 요청 시도 처리 초과");
                log.warn("인덱스 요청 본문 : {}" , reindexRequest);
                throw new IllegalArgumentException(e.getMessage());
            }
            count = count + 1 ;
            log.info("요청 재시도 : {}" , count);
            log.info("인덱스 요청 본문 : {}" , reindexRequest);
            reindexRequest(reindexRequest,count);
        }
    }

    private String submitReindexTask(RestHighLevelClient client, String sourceIndex, String destIndex) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        RequestOptions largeBufferRequestOptions = getBean(RequestOptions.class);

        String requestBody = String.format(
                "{\"source\":{\"index\":\"%s\"},\"dest\":{\"index\":\"%s\"}}",
                sourceIndex, destIndex
        );

        Request request = new Request("POST", "/_reindex?wait_for_completion=false&refresh=true");
        request.setJsonEntity(requestBody);
        request.setOptions(largeBufferRequestOptions);

        Response response = client.getLowLevelClient().performRequest(request);
        String responseBody = EntityUtils.toString(response.getEntity());
        Map<String, Object> responseMap = objectMapper.readValue(responseBody, new TypeReference<>() {});

        return (String) responseMap.get("task");
    }

    private Map<String, Object> waitForTaskCompletion(RestHighLevelClient client, String taskId) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> responseMap;

        while (true) {
            Thread.sleep(10000);
            try {
                Request request = new Request("GET", "/_tasks/" + taskId);
                Response response = client.getLowLevelClient().performRequest(request);
                responseMap = objectMapper.readValue(EntityUtils.toString(response.getEntity()), new TypeReference<>() {});

                if ((Boolean) responseMap.get("completed")) {
                    return responseMap;
                }
                log.info("리인덱스 진행 중 - taskId: {}", taskId);
            } catch (Exception e) {
                log.warn("태스크 조회 실패 (태스크가 사라졌을 수 있음) - taskId: {}, error: {}", taskId, e.getMessage());
                throw new IllegalStateException("리인덱스 태스크 조회 실패 - taskId: " + taskId + ", 태스크가 사라졌거나 클러스터 장애 발생");
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void logReindexResult(String taskId, Map<String, Object> taskResult) {
        Map<String, Object> error = (Map<String, Object>) taskResult.get("error");
        if (error != null) {
            throw new IllegalStateException("리인덱스 실패 - taskId: " + taskId + ", error: " + error);
        }
        log.info("리인덱스 완료 - taskId: {}, response: {}", taskId, taskResult.get("response"));
    }

    @Override
    public List<CatIndexVO> catIndexVOList(){
        IndexCoordinates indexCoordinatesFor = operations.getIndexCoordinatesFor(entityClass);
        String indexName = indexCoordinatesFor.getIndexName();
        RestHighLevelClient restHighLevelClient = getBean(RestHighLevelClient.class);
        try{
            ObjectMapper objectMapper = new ObjectMapper();
            Response response = restHighLevelClient.getLowLevelClient().performRequest(
                new Request("GET", "/_cat/indices/"+indexName+"?format=json")
            );
            String responseBody = EntityUtils.toString(response.getEntity());
            return objectMapper.readValue(responseBody, new TypeReference<>() {});
        }catch (Exception e){
            throw new IllegalArgumentException(e.getMessage());
        }

    }

    @Override
    public Long indexCount(){
        RestHighLevelClient restHighLevelClient = getBean(RestHighLevelClient.class);
        try{
            ObjectMapper objectMapper = new ObjectMapper();
            Response response = restHighLevelClient.getLowLevelClient().performRequest(
                    new Request("GET", "/_alias/"+this.indexAliasName())
            );
            String responseBody = EntityUtils.toString(response.getEntity());
            return (long)objectMapper.readValue(responseBody, HashMap.class).size();

        }catch (Exception e){
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    private Set<String> indexNames() {
        IndexCoordinates indexCoordinatesFor = operations.getIndexCoordinatesFor(entityClass);
        String indexName = indexCoordinatesFor.getIndexName();
        IndexOperations indexOperations = operations.indexOps(IndexCoordinates.of(indexName));
        return indexOperations.getAliasesForIndex(indexName).keySet();
    }

    private List<String> indexWithDayRange(int day){
        Set<String> indexNames = this.indexNames();
        List<String> days = dayList(day);
        return indexNames.stream()
            .filter(indexName -> !days.contains(indexName)
                && indexName.split("-",2)[1].replace("-", "").lastIndexOf("00") != 6)
            .collect(toList());
    }

    private void deleteIndexByMonth(IndexCoordinates indexCoordinates) {
        IndexOperations indexOperations = operations.indexOps(indexCoordinates);
        indexOperations.delete();
    }

    private String findDateFormat(String date){

        String[] formats = {"yyyy-MM-dd", "yyyyMMdd"};

        for (String format : formats) {
            SimpleDateFormat sdf = new SimpleDateFormat(format);
            sdf.setLenient(false);
            try {
                sdf.parse(date);
                return format;
            }  catch (ParseException ignored) {
                return formats[0];
            }
        }
        return formats[0];

    }

    private List<String> dayList(int range) {

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());

        calendar.add(Calendar.DATE, -(Math.abs(range) - 1));
        Date daysAgo = calendar.getTime();

        calendar.setTime(daysAgo);

        return IntStream.range(0, range).boxed().map(a -> {
            String day = defaultFormater.format(calendar.getTime());
            calendar.add(Calendar.DATE, 1);
            return this.indexAliasName()+"-"+ day;
        }).collect(Collectors.toList());

    }

}
