package org.skywalking.apm.collector.worker.segment.persistence;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.skywalking.apm.collector.actor.*;
import org.skywalking.apm.collector.actor.selector.RollingSelector;
import org.skywalking.apm.collector.actor.selector.WorkerSelector;
import org.skywalking.apm.collector.worker.segment.SegmentCostIndex;
import org.skywalking.apm.collector.worker.segment.SegmentExceptionIndex;
import org.skywalking.apm.collector.worker.segment.SegmentIndex;
import org.skywalking.apm.collector.worker.segment.entity.GlobalTraceId;
import org.skywalking.apm.collector.worker.segment.entity.Segment;
import org.skywalking.apm.collector.worker.segment.entity.SegmentDeserialize;
import org.skywalking.apm.collector.worker.storage.EsClient;
import org.skywalking.apm.collector.worker.tools.CollectionTools;

import java.util.List;

/**
 * @author pengys5
 */
public class SegmentTopSearchWithTimeSlice extends AbstractLocalSyncWorker {

    private SegmentTopSearchWithTimeSlice(Role role, ClusterWorkerContext clusterContext,
                                          LocalWorkerContext selfContext) {
        super(role, clusterContext, selfContext);
    }

    @Override
    public void preStart() throws ProviderNotFoundException {
        getClusterContext().findProvider(SegmentExceptionWithSegId.WorkerRole.INSTANCE).create(this);
    }

    @Override
    protected void onWork(Object request, Object response) throws Exception {
        if (request instanceof RequestEntity) {
            RequestEntity search = (RequestEntity) request;

            SearchRequestBuilder searchRequestBuilder = EsClient.INSTANCE.getClient().prepareSearch(SegmentCostIndex.INDEX);
            searchRequestBuilder.setTypes(SegmentCostIndex.TYPE_RECORD);
            searchRequestBuilder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            searchRequestBuilder.setQuery(boolQueryBuilder);
            boolQueryBuilder.must().add(QueryBuilders.rangeQuery(SegmentCostIndex.TIME_SLICE).gte(search.startTime).lte(search.endTime));
            if (search.minCost != -1 || search.maxCost != -1) {
                RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(SegmentCostIndex.COST);
                if (search.minCost != -1) {
                    rangeQueryBuilder.gte(search.minCost);
                }
                if (search.maxCost != -1) {
                    rangeQueryBuilder.lte(search.maxCost);
                }
                boolQueryBuilder.must().add(rangeQueryBuilder);
            }

            searchRequestBuilder.addSort(SegmentCostIndex.COST, SortOrder.DESC);
            searchRequestBuilder.setSize(search.limit);
            searchRequestBuilder.setFrom(search.from);

            SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();

            JsonObject topSegPaging = new JsonObject();
            topSegPaging.addProperty("recordsTotal", searchResponse.getHits().totalHits());

            JsonArray topSegArray = new JsonArray();
            topSegPaging.add("data", topSegArray);

            int num = search.from;
            for (SearchHit searchHit : searchResponse.getHits().getHits()) {
                JsonObject topSegmentJson = new JsonObject();
                topSegmentJson.addProperty("num", num);
                String segId = (String) searchHit.getSource().get(SegmentCostIndex.SEG_ID);
                topSegmentJson.addProperty(SegmentCostIndex.SEG_ID, segId);
                topSegmentJson.addProperty(SegmentCostIndex.START_TIME, (Number) searchHit.getSource().get(SegmentCostIndex.START_TIME));
                if (searchHit.getSource().containsKey(SegmentCostIndex.END_TIME)) {
                    topSegmentJson.addProperty(SegmentCostIndex.END_TIME, (Number) searchHit.getSource().get(SegmentCostIndex.END_TIME));
                }

                topSegmentJson.addProperty(SegmentCostIndex.OPERATION_NAME, (String) searchHit.getSource().get(SegmentCostIndex.OPERATION_NAME));
                topSegmentJson.addProperty(SegmentCostIndex.COST, (Number) searchHit.getSource().get(SegmentCostIndex.COST));

                String segmentSource = EsClient.INSTANCE.getClient().prepareGet(SegmentIndex.INDEX, SegmentIndex.TYPE_RECORD, segId).get().getSourceAsString();
                logger().debug("segmentSource:" + segmentSource);
                Segment segment = SegmentDeserialize.INSTANCE.deserializeSingle(segmentSource);
                List<GlobalTraceId> distributedTraceIdList = segment.getRelatedGlobalTraces();

                JsonArray distributedTraceIdArray = new JsonArray();
                if (CollectionTools.isNotEmpty(distributedTraceIdList)) {
                    for (GlobalTraceId distributedTraceId : distributedTraceIdList) {
                        distributedTraceIdArray.add(distributedTraceId.get());
                    }
                }
                topSegmentJson.add("traceIds", distributedTraceIdArray);

                boolean isError = false;
                JsonObject resJsonObj = new JsonObject();
                getSelfContext().lookup(SegmentExceptionWithSegId.WorkerRole.INSTANCE).ask(new SegmentExceptionWithSegId.RequestEntity(segId), resJsonObj);
                if (resJsonObj.has("result")) {
                    JsonObject segExJson = resJsonObj.get("result").getAsJsonObject();
                    if (segExJson.has(SegmentExceptionIndex.IS_ERROR)) {
                        isError = segExJson.get(SegmentExceptionIndex.IS_ERROR).getAsBoolean();
                    }
                }
                topSegmentJson.addProperty(SegmentExceptionIndex.IS_ERROR, isError);

                num++;
                topSegArray.add(topSegmentJson);
            }

            JsonObject resJsonObj = (JsonObject) response;
            resJsonObj.add("result", topSegPaging);
        }
    }

    public static class RequestEntity {
        private int from;
        private int limit;
        private long startTime;
        private long endTime;
        private int minCost;
        private int maxCost;

        public RequestEntity(int from, int limit, long startTime, long endTime) {
            this.from = from;
            this.limit = limit;
            this.startTime = startTime;
            this.endTime = endTime;
        }

        public void setMinCost(int minCost) {
            this.minCost = minCost;
        }

        public void setMaxCost(int maxCost) {
            this.maxCost = maxCost;
        }

        public int getFrom() {
            return from;
        }

        public int getLimit() {
            return limit;
        }

        public long getStartTime() {
            return startTime;
        }

        public long getEndTime() {
            return endTime;
        }

        public int getMinCost() {
            return minCost;
        }

        public int getMaxCost() {
            return maxCost;
        }
    }

    public static class Factory extends AbstractLocalSyncWorkerProvider<SegmentTopSearchWithTimeSlice> {
        @Override
        public Role role() {
            return WorkerRole.INSTANCE;
        }

        @Override
        public SegmentTopSearchWithTimeSlice workerInstance(ClusterWorkerContext clusterContext) {
            return new SegmentTopSearchWithTimeSlice(role(), clusterContext, new LocalWorkerContext());
        }
    }

    public enum WorkerRole implements Role {
        INSTANCE;

        @Override
        public String roleName() {
            return SegmentTopSearchWithTimeSlice.class.getSimpleName();
        }

        @Override
        public WorkerSelector workerSelector() {
            return new RollingSelector();
        }
    }
}
