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

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.Client;
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.globaltrace.GlobalTraceIndex;
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.storage.JoinAndSplitData;
import org.skywalking.apm.collector.worker.tools.CollectionTools;

import java.util.List;

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

    private Gson gson = new Gson();

    private SegmentTopSearchWithGlobalTraceId(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;
            Client client = EsClient.INSTANCE.getClient();

            String globalTraceData = client.prepareGet(GlobalTraceIndex.INDEX, GlobalTraceIndex.TYPE_RECORD, search.globalTraceId).get().getSourceAsString();
            JsonObject globalTraceObj = gson.fromJson(globalTraceData, JsonObject.class);

            JsonObject topSegPaging = new JsonObject();
            topSegPaging.addProperty("recordsTotal", 0);

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

            if (globalTraceObj != null && globalTraceObj.has(GlobalTraceIndex.SUB_SEG_IDS)) {
                String subSegIdsStr = globalTraceObj.get(GlobalTraceIndex.SUB_SEG_IDS).getAsString();
                String[] subSegIds = subSegIdsStr.split(JoinAndSplitData.SPLIT);

                topSegPaging.addProperty("recordsTotal", subSegIds.length);

                int num = search.from;
                int limit = search.limit;
                if (search.limit >= subSegIds.length) {
                    limit = subSegIds.length;
                }

                for (int i = num; i < limit; i++) {
                    GetResponse getResponse = client.prepareGet(SegmentCostIndex.INDEX, SegmentCostIndex.TYPE_RECORD, subSegIds[num]).get();

                    JsonObject topSegmentJson = new JsonObject();
                    topSegmentJson.addProperty("num", num);
                    String segId = (String) getResponse.getSource().get(SegmentCostIndex.SEG_ID);
                    topSegmentJson.addProperty(SegmentCostIndex.SEG_ID, segId);
                    topSegmentJson.addProperty(SegmentCostIndex.START_TIME, (Number) getResponse.getSource().get(SegmentCostIndex.START_TIME));
                    if (getResponse.getSource().containsKey(SegmentCostIndex.END_TIME)) {
                        topSegmentJson.addProperty(SegmentCostIndex.END_TIME, (Number) getResponse.getSource().get(SegmentCostIndex.END_TIME));
                    }

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

                    String segmentSource = client.prepareGet(SegmentIndex.INDEX, SegmentIndex.TYPE_RECORD, segId).get().getSourceAsString();
                    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 String globalTraceId;

        public RequestEntity(String globalTraceId, int from, int limit) {
            this.from = from;
            this.limit = limit;
            this.globalTraceId = globalTraceId;
        }

        public int getFrom() {
            return from;
        }

        public int getLimit() {
            return limit;
        }

        public String getGlobalTraceId() {
            return globalTraceId;
        }
    }

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

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

    public enum WorkerRole implements Role {
        INSTANCE;

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

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