/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.controller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.domain.vo.MetricVo;

/**
 * @author leyou
 */
@Controller
@RequestMapping(value = "/metric", produces = MediaType.APPLICATION_JSON_VALUE)
public class MetricController {

	private static Logger logger = LoggerFactory.getLogger(MetricController.class);

	private static final long maxQueryIntervalMs = 1000 * 60 * 60;

	@Autowired
	private MetricsRepository<MetricEntity> metricStore;

	@ResponseBody
	@RequestMapping("/queryTopResourceMetric.json")
	public Result<?> queryTopResourceMetric(final String app, Integer pageIndex, Integer pageSize, Boolean desc,
			Long startTime, Long endTime, String searchKey) {
		if (StringUtil.isEmpty(app)) {
			return Result.ofFail(-1, "app can't be null or empty");
		}
		if (pageIndex == null || pageIndex <= 0) {
			pageIndex = 1;
		}
		if (pageSize == null) {
			pageSize = 6;
		}
		if (pageSize >= 20) {
			pageSize = 20;
		}
		if (desc == null) {
			desc = true;
		}
		if (endTime == null) {
			endTime = System.currentTimeMillis();
		}
		if (startTime == null) {
			startTime = endTime - 1000 * 60 * 5;
		}
		if (endTime - startTime > maxQueryIntervalMs) {
			return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
		}
		List<String> resources = metricStore.listResourcesOfApp(app);
		logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size());

		if (resources == null || resources.isEmpty()) {
			return Result.ofSuccess(null);
		}
		if (!desc) {
			Collections.reverse(resources);
		}
		if (StringUtil.isNotEmpty(searchKey)) {
			List<String> searched = new ArrayList<>();
			for (String resource : resources) {
				if (resource.contains(searchKey)) {
					searched.add(resource);
				}
			}
			resources = searched;
		}
		int totalPage = (resources.size() + pageSize - 1) / pageSize;
		List<String> topResource = new ArrayList<>();
		if (pageIndex <= totalPage) {
			topResource = resources.subList((pageIndex - 1) * pageSize,
					Math.min(pageIndex * pageSize, resources.size()));
		}
		final Map<String, Iterable<MetricVo>> map = new ConcurrentHashMap<>();
		logger.debug("topResource={}", topResource);
		long time = System.currentTimeMillis();
		for (final String resource : topResource) {
			List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(app, resource, startTime, endTime);
			logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size());
			List<MetricVo> vos = MetricVo.fromMetricEntities(entities, resource);
			Iterable<MetricVo> vosSorted = sortMetricVoAndDistinct(vos);
			map.put(resource, vosSorted);
		}
		logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time);
		Map<String, Object> resultMap = new HashMap<>(16);
		resultMap.put("totalCount", resources.size());
		resultMap.put("totalPage", totalPage);
		resultMap.put("pageIndex", pageIndex);
		resultMap.put("pageSize", pageSize);

		Map<String, Iterable<MetricVo>> map2 = new LinkedHashMap<>();
		// order matters.
		for (String identity : topResource) {
			map2.put(identity, map.get(identity));
		}
		resultMap.put("metric", map2);
		return Result.ofSuccess(resultMap);
	}

	@ResponseBody
	@RequestMapping("/queryByAppAndResource.json")
	public Result<?> queryByAppAndResource(String app, String identity, Long startTime, Long endTime) {
		if (StringUtil.isEmpty(app)) {
			return Result.ofFail(-1, "app can't be null or empty");
		}
		if (StringUtil.isEmpty(identity)) {
			return Result.ofFail(-1, "identity can't be null or empty");
		}
		if (endTime == null) {
			endTime = System.currentTimeMillis();
		}
		if (startTime == null) {
			startTime = endTime - 1000 * 60;
		}
		if (endTime - startTime > maxQueryIntervalMs) {
			return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
		}
		List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(app, identity, startTime, endTime);
		List<MetricVo> vos = MetricVo.fromMetricEntities(entities, identity);
		return Result.ofSuccess(sortMetricVoAndDistinct(vos));
	}

	private Iterable<MetricVo> sortMetricVoAndDistinct(List<MetricVo> vos) {
		if (vos == null) {
			return null;
		}
		Map<Long, MetricVo> map = new TreeMap<>();
		for (MetricVo vo : vos) {
			MetricVo oldVo = map.get(vo.getTimestamp());
			if (oldVo == null || vo.getGmtCreate() > oldVo.getGmtCreate()) {
				map.put(vo.getTimestamp(), vo);
			}
		}
		return map.values();
	}

}
