/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.gateway.route;

import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.alibaba.nacos.api.exception.NacosException;
import com.crunii.micro.common.utils.JsonUtil;
import com.crunii.micro.common.utils.ResUtil;
import com.crunii.micro.common.utils.YmlUtil;
import com.crunii.micro.component.redis.config.RedissonReactorAutoConfiguration;
import com.crunii.micro.gateway.GatewayConstants;
import com.crunii.micro.gateway.limit.GuavaBurstyRateLimiter;
import com.crunii.micro.gateway.limit.GuavaWarmingUpRateLimiter;
import com.crunii.micro.gateway.nacos.DynamicRouteConverter;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.synchronizedMap;

/**
 * @author 田平 create 2019年6月26日上午10:29:51
 */
@Slf4j
@AutoConfigureAfter(RedissonReactorAutoConfiguration.class)
@Component
public class DynamicRouteDefinitionRepository implements RouteDefinitionRepository {
	private final Map<String, RouteDefinition> routesCache = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());

	@Autowired
	private ApplicationEventPublisher publisher;

	@NacosInjected
	private ConfigService configService;

	@PostConstruct
	public void init() {
		try {
			String config = configService.getConfig(GatewayConstants.DYNAMIC_ROUTES_DATA_ID, null, 30000L);
			if (StringUtils.hasText(config)) {
				List<RouteDefinition> routes = DynamicRouteConverter.convertConfig(config);
				routes.stream().forEach(r -> {
					routesCache.put(r.getId(), r);
				});
				log.info("init dynamic routes size:" + routes.size());
			}
		} catch (NacosException e) {
			throw new RuntimeException(e);
		}
	}

	@NacosConfigListener(dataId = GatewayConstants.DYNAMIC_ROUTES_DATA_ID, converter = DynamicRouteConverter.class, timeout = 10000)
	public void onRouteChange(List<RouteDefinition> routes) {
		routesCache.entrySet().forEach(r -> log.info("remove OldRoute:" + r.toString()));
		routesCache.clear();
		// 清空限流配置
		GuavaWarmingUpRateLimiter.cacheLimiter.clear();
		GuavaBurstyRateLimiter.cacheLimiter.clear();
		if (routes != null) {
			routes.stream().forEach(r -> {
				log.info("add NewRoute:" + r.toString());
				routesCache.put(r.getId(), r);
			});
		}
		publisher.publishEvent(new RefreshRoutesEvent(this));
	}

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			routesCache.put(r.getId(), r);
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		return routeId.flatMap(id -> {
			if (routesCache.containsKey(id)) {
				routesCache.remove(id);
				return Mono.empty();
			}
			return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
		});
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		return Flux.fromIterable(routesCache.values());
	}

	public static void main(String[] args) throws Exception {
		String path = "spring.cloud.gateway.routes";
		String yml = ResUtil.getResContent("classpath:application-dynamic-routes.yml");
		JsonNode routesNode = YmlUtil.getJsonNodeByPath(yml, path);
		List<RouteDefinition> routeDefinitions = JsonUtil.jsonToList(routesNode.toString(), RouteDefinition.class);
		routeDefinitions.forEach(System.out::println);
	}

}
