package com.sf.vertx.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.sf.vertx.api.pojo.AppConfig;
import com.sf.vertx.api.pojo.Node;
import com.sf.vertx.api.pojo.RouteContent;
import com.sf.vertx.api.pojo.SacService;
import com.sf.vertx.api.pojo.Strategy;
import com.sf.vertx.api.pojo.VertxConfig;
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
import com.sf.vertx.constans.RedisKeyConfig;
import com.sf.vertx.service.AppConfigService;
import com.sf.vertx.utils.ProxyTool;
import com.sf.vertx.utils.SpringUtils;

import cn.hutool.core.collection.ConcurrentHashSet;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class AppConfigServiceImpl implements AppConfigService {
	@Value("${server.vertx.environment}")
	private String vertxEnvironment;
	@Autowired
	private RedisTemplate<String, String> redisTemplate;
	private static VertxConfig VERTX_CONFIG = new VertxConfig();
	private static final ConcurrentHashMap<String, AppConfig> CACHE_APP_CONFIG = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap<String, Strategy> CACHE_APP_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap<String, Strategy> CACHE_API_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap<String, SacService> CACHE_APP_SERVICE = new ConcurrentHashMap<>();
	private static ConcurrentHashMap<String, SacLoadBalancing> SAC_LOADBALANCING_MAP = new ConcurrentHashMap<String, SacLoadBalancing>();
	private static ConcurrentHashSet<String> CACHE_DISABLED_APP = new ConcurrentHashSet<String>();

	@SuppressWarnings("unchecked")
	public static void addAddressRetryStrategy(String address, String appCode) {
		String setKey = RedisKeyConfig.VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY + ":" + appCode;
		String key = RedisKeyConfig.VERTX_ADDRESS_RETRY_STRATEGY_KEY + ":" + appCode + ":" + address;
		RedisTemplate<String, String> redisTemplate = SpringUtils.getBean("redisTemplate", RedisTemplate.class);
		Long thresholdCount = redisTemplate.opsForValue().increment(key);
		// log.info("redis app threshold: {}", redisTemplate.opsForValue().get(key));
		Integer timeWindow = VERTX_CONFIG.getAddressRetryStrategy() != null
				&& VERTX_CONFIG.getAddressRetryStrategy().getTimeWindow() > 0
						? VERTX_CONFIG.getAddressRetryStrategy().getTimeWindow()
						: 20;
		if (thresholdCount == 1) { // 创建,才设置时间窗口
			redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
		}
		Integer threshold = AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy() != null
				&& AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getThreshold() > 0
						? AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getThreshold()
						: 3;
		if (thresholdCount > threshold) {
			// 设置服务不可用
			redisTemplate.opsForZSet().add(setKey, appCode + ":" + address, 0);
		}
	}

	public static boolean appDataSecurity(String appCode) {
		return CACHE_APP_CONFIG.get(appCode) != null && CACHE_APP_CONFIG.get(appCode).getDataSecurity() != null ? true
				: false;
	}

	public static Strategy getAppCurrentLimitingConfig(String appCode) {
		return CACHE_APP_CURRENT_LIMITING_CONFIG.get(appCode);
	}

	public static Strategy getApiCurrentLimitingConfig(String appCode) {
		return CACHE_API_CURRENT_LIMITING_CONFIG.get(appCode);
	}

	public static AppConfig getAppConfig(String appCode) {
		return CACHE_APP_CONFIG.get(appCode);
	}

	public static VertxConfig getVertxConfig() {
		return VERTX_CONFIG;
	}

	public static String getSacAppHeaderKey() {
		return VERTX_CONFIG.getAppHeaderKey() != null ? VERTX_CONFIG.getAppHeaderKey() : "sacAppCode";
	}

	public static String getAppHeaderServiceName() {
		return VERTX_CONFIG.getAppHeaderServiceName() != null ? VERTX_CONFIG.getAppHeaderServiceName()
				: "sacAppServiceName";
	}

	/***
	 * 加载vertx配置
	 */
	public void initVertxConfig() {
		String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
		String vertxConfigValue = redisTemplate.opsForValue().get(vertxConfigKey);
		if (StringUtils.isNotBlank(vertxConfigValue)) {
			VERTX_CONFIG = JSONObject.parseObject(vertxConfigValue, VertxConfig.class);
		}
	}

	private void initAppConfig(String appCode) {
		String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
		String appCodeValue = redisTemplate.opsForValue().get(appCodeKey);
		if (StringUtils.isNotBlank(appCodeValue)) {
			AppConfig appConfig = JSON.parseObject(appCodeValue, new TypeReference<AppConfig>() {
			});
			CACHE_APP_CONFIG.put(appCode, appConfig);

			// app、api默认限流
			if(appConfig.getApiCurrentLimitingConfig() != null) {
				CACHE_API_CURRENT_LIMITING_CONFIG.put(appCode, appConfig.getApiCurrentLimitingConfig());
			}
			
			if(appConfig.getAppCurrentLimitingConfig() != null) {
				CACHE_APP_CURRENT_LIMITING_CONFIG.put(appCode, appConfig.getAppCurrentLimitingConfig());
			}
			

			// app router负载均衡
			for (SacService sacService : appConfig.getService()) {
				CACHE_APP_SERVICE.put(appCode + ";" + sacService.getServiceName(), sacService);
				List<Node> nodeList = new ArrayList<>();
				// 获取service模式
				if (StringUtils.equals(sacService.getServiceModel(), "NORMAL")) {
					Node node = new Node();
					node.setIp(sacService.getServerAddress().getHost());
					node.setPort(sacService.getServerAddress().getPort());
					node.setWeight(0);
					node.setProtocol(sacService.getServerAddress().getProtocol());
					nodeList.add(node);
				} else if (StringUtils.equals(sacService.getServiceModel(), "ROUTE")) {
					if (sacService.getRouteConfig() != null
							&& StringUtils.equals(sacService.getRouteConfig().getRouteType(), "WEIGHT_ROUTE")) {
						for (RouteContent routeContent : sacService.getRouteConfig().getRouteContent()) {
							Node node = new Node();
							node.setIp(routeContent.getServerAddress().getHost());
							node.setPort(routeContent.getServerAddress().getPort());
							node.setWeight(routeContent.getWeight() != null && routeContent.getWeight() > 0
									? routeContent.getWeight()
									: 0);
							node.setProtocol(routeContent.getServerAddress().getProtocol());
							nodeList.add(node);
						}
					}
				}

				if (nodeList.size() > 0) {
					// 初始化负载均衡算法
					String key = appCode + ";" + sacService.getServiceName();
					SacLoadBalancing sacLoadBalancing = ProxyTool.roundRobin(nodeList);
					SAC_LOADBALANCING_MAP.put(key, sacLoadBalancing);
				}

			}
		}
	}

	/***
	 * 从redis加载数据
	 * 
	 * @throws Exception
	 */
	public void initAllAppConfig() throws Exception {
		Set<String> set = redisTemplate.opsForZSet().range(RedisKeyConfig.APP_CONFIG_SET_KEY, 0, -1);
		for (String appCode : set) {
			initAppConfig(appCode);
		}
	}

	public static SacLoadBalancing getSacLoadBalancing(String appCode, String serviceName) {
		return SAC_LOADBALANCING_MAP.get(appCode + ";" + serviceName);
	}

	public static SacService getSacService(String appCode, String serviceName) {
		return CACHE_APP_SERVICE.get(appCode + ";" + serviceName);
	}

	/***
	 * 新增、修改
	 * 
	 * @param appConfig
	 */
	public void saveAppConfig(String appConfigStr) {
		AppConfig appConfig = JSON.parseObject(appConfigStr, AppConfig.class);
		redisTemplate.opsForZSet().add(RedisKeyConfig.APP_CONFIG_SET_KEY, appConfig.getAppCode(), 0);
		String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appConfig.getAppCode();
		redisTemplate.opsForValue().set(appCodeKey, appConfigStr);
		
		// 初始化AppConfig本地缓存
		initAppConfig(appConfig.getAppCode());
		
		// 删除禁用app列表
		CACHE_DISABLED_APP.remove(appConfig.getAppCode());
	}

	/***
	 * 删除
	 * 
	 * @param appConfig
	 */
	public void deleteAppConfig(AppConfig appConfig) {
		redisTemplate.opsForZSet().remove(RedisKeyConfig.APP_CONFIG_SET_KEY, appConfig.getAppCode());
		String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appConfig.getAppCode();
		redisTemplate.delete(appCodeKey);
		
		// 不动本地缓存, 入口控制app被禁用, 项目启动会加载最新配置
		CACHE_DISABLED_APP.add(appConfig.getAppCode());
	}

	/***
	 * 新增、修改
	 * 
	 * @param appConfig
	 */
	public void saveVertxConfig(VertxConfig vertxConfig) {
		String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
		redisTemplate.opsForValue().set(vertxConfigKey, JSONObject.toJSONString(vertxConfig));
		initVertxConfig();
	}
}
