package com.union.ai.api.aspect;

import com.alibaba.fastjson.JSON;
import com.union.ai.api.common.consts.RocketMqConsts;
import com.union.ai.api.consts.RedisKeys;
import com.union.ai.api.dao.model.ApiLog;
import com.union.ai.api.dao.model.App;
import com.union.ai.api.dao.model.AppApi;
import com.union.ai.api.dto.Result;
import com.union.ai.api.enums.ErrorEnum;
import com.union.ai.api.helper.DefaultProducerHelper;
import com.union.ai.api.service.AppApiService;
import com.union.ai.api.service.AppService;
import com.union.ai.api.util.RedisUtil;
import com.union.ai.api.util.ThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by IntelliJ IDEA.
 * Author: xingxin.jing
 * Date: 2018/12/15
 * Time: 上午8:44
 * 监控点controller
 */
@Slf4j
public abstract class CommonSecurityAspect {


	private DefaultProducerHelper producer = DefaultProducerHelper.getSingleton();

	@Autowired
	private AppApiService appApiService;

	@Autowired
	private AppService appService;

	private static ExecutorService executorService = Executors.newFixedThreadPool(5);

	/**
	 * 前置通知
	 * @param joinPoint
	 */
	public abstract Object beforeMethod(ProceedingJoinPoint joinPoint) throws Throwable;

	protected Object proceed(ProceedingJoinPoint joinPoint, String param) throws Throwable {

		ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = ra.getRequest();

		Integer appId = null;
		String token = request.getHeader("access_token");

		log.info("access_token:"+token);

		long start = System.currentTimeMillis();
		try {
			if (StringUtils.isBlank(token))
			{
//				send(start, System.currentTimeMillis(), appId, null, param, ErrorEnum.ERROR_110000);
				return new Result(ErrorEnum.ERROR_110000).toString();
			}
			String requestURI = request.getRequestURI();
			String appIdStr = RedisUtil.get(token);

			log.info("appIdStr:"+appIdStr +"  requestURI:"+requestURI);

			if (StringUtils.isBlank(appIdStr))
			{
//			    send(start, System.currentTimeMillis(), appId, null, param, ErrorEnum.ERROR_110000);
				return new Result(ErrorEnum.ERROR_110000).toString();
			}
			ThreadLocalUtil.appIdThreadLocal.set(appIdStr);
			appId = Integer.valueOf(appIdStr);
			AppApi appApi = appApiService.queryByAppAndUrl(appId, requestURI);
			Integer apiId = null;
			App app = appService.getEnableAppInfo(appId);
			if (appApi == null || app ==null)
			{
				send(start, System.currentTimeMillis(),app.getUserId(), appId, apiId, param, ErrorEnum.ERROR_130000);
                return new Result(ErrorEnum.ERROR_130000).toString();
            }

			log.info("appApi:"+JSON.toJSONString(appApi));

            apiId = appApi.getApiId();
            String qpsLimit = appApi.getQpsLimit();
			String callLimit = appApi.getCallLimit();
			Long qps = 10l;
			Long call = 1000l;
			if (StringUtils.isNotBlank(qpsLimit)) {
				qps = Long.valueOf(qpsLimit);
			}
			if (StringUtils.isNotBlank(callLimit)) {
				call = Long.valueOf(callLimit);
			}
			if (StringUtils.isNotBlank(appApi.getUsed())) {
				Long used = Long.valueOf(appApi.getUsed());
				if (call.equals(used))
				{
					send(start, System.currentTimeMillis(), app.getUserId(),appId, apiId, param, ErrorEnum.ERROR_121000);
					return new Result(ErrorEnum.ERROR_121000).toString();
				}
			}
			//qpslimit
			int second = Calendar.getInstance().get(Calendar.SECOND);
			String before = String.valueOf(appId) + apiId;
			String rateKey = before + second;
			int i = RedisUtil.incr(rateKey).intValue();
			if (i > qps) {
				send(start, System.currentTimeMillis(),app.getUserId(), appId, apiId, param, ErrorEnum.ERROR_120000);
				return new Result(ErrorEnum.ERROR_120000).toString();
			} else if (i == 1) {
				RedisUtil.expire(rateKey, 5);
			}
			//daylimit
			String callKey = String.format(RedisKeys.CALL_LIMIT_S, appId, apiId);
			Long callNum = RedisUtil.incr(callKey);
			log.info("callKey={},调用次数:{}", callKey, callNum);
			if (callNum > call) {
				send(start, System.currentTimeMillis(), app.getUserId(),appId, apiId, param, ErrorEnum.ERROR_121000);
				RedisUtil.set(callKey, call.toString());
				return new Result(ErrorEnum.ERROR_121000).toString();
			}
			Object proceed = joinPoint.proceed();
			long end = System.currentTimeMillis();
			send(start, end,app.getUserId(), appId, apiId, param, proceed);
			return proceed;
		} catch (Exception e) {
			log.error("appId=" + appId + "    token=" + token + "  验证权限报错 ",e);
			return new Result(ErrorEnum.ERROR_140000).toString();
		} finally {
			ThreadLocalUtil.appIdThreadLocal.remove();
		}
	}

	private void send(final long start, final long end,final Integer userId, final Integer appId,final Integer apiId, final String param, final Object proceed) {
		if (appId == null || apiId == null) {
			log.warn("appId={},apiId={}不能为空", appId, apiId);
			return;
		}
		executorService.execute(new Runnable() {
			@Override
			public void run() {

				ApiLog apiLog = new ApiLog();
				apiLog.setApiId(apiId);
				apiLog.setAppId(appId);
				apiLog.setUserId(userId);
				apiLog.setInvokeTime(String.valueOf(start));
				apiLog.setInvokeDate(new DateTime().toDate());
				apiLog.setResponseTime(String.valueOf(end));
				apiLog.setParams(param);
				try
				{
					if (proceed == null) {
						apiLog.setCallResult(0);
					} else {
						Integer err_no = JSON.parseObject(proceed.toString()).getInteger("err_no");
						if (err_no == null) {
							err_no = JSON.parseObject(proceed.toString()).getInteger("error_code");
						}
						if (err_no == null || err_no != 0) {
							apiLog.setCallResult(0);
							if (err_no != null) {
								apiLog.setErrCode(err_no.toString());
							}
						} else {
							apiLog.setErrCode("0");
							apiLog.setCallResult(1);
						}
					}
					log.info("appId={},apiId={} apiLog.toString()：{}", appId, apiId, JSON.toJSONString(apiLog));

					SendResult send = producer.send(RocketMqConsts.AI_MQ_TOPIC, JSON.toJSONString(apiLog));
					if (send != null)
					{
						log.info("appId={},apiId={}访问发送日志结果：{}", appId, apiId, JSON.toJSONString(send));
					}
					else
					{
						log.info("appId={},apiId={}访问发送日志结果：日志发送失败!", appId, apiId);
					}
				}
				catch (Exception e) {
					//不打印参数到日志
					apiLog.setParams(null);
					log.error("发送数据失败："+  e.getMessage());
					log.error("发送数据失败："+ JSON.toJSONString(apiLog), e);
				}
			}
		});
	}

	private void send(final long start, final long end,final Integer userId, final Integer appId,final Integer apiId, final String param, final ErrorEnum error) {

		if (appId == null || apiId == null) {
			log.warn("appId={},apiId={}不能为空", appId, apiId);
			return;
		}

		executorService.execute(new Runnable() {
			@Override
			public void run() {
				ApiLog apiLog = new ApiLog();
				apiLog.setApiId(apiId);
				apiLog.setAppId(appId);
				apiLog.setUserId(userId);
				apiLog.setInvokeTime(String.valueOf(start));
				apiLog.setResponseTime(String.valueOf(end));
				apiLog.setInvokeDate(new DateTime().toDate());
				apiLog.setParams(param);
				apiLog.setCallResult(0);
				apiLog.setErrCode(String.valueOf(error.getCode()));
				try {
//					log.info("appId={},apiId={} apiLog.toString()：{}", appId, apiId, JSON.toJSONString(apiLog));
				    SendResult sendResult=	producer.send(RocketMqConsts.AI_MQ_TOPIC, JSON.toJSONString(apiLog));
					if (sendResult != null)
					{
						apiLog.setParams(null);
						log.info("appId={},apiId={}访问发送日志结果：{}", appId, apiId, sendResult);
					}
					else
					{
						log.info("appId={},apiId={}访问发送日志结果：日志发送失败!", appId, apiId);
					}
				} catch (Exception e) {
					//不打印参数到日志

					log.error("发送数据失败：",  e);
//					log.error("发送数据失败："+ JSON.toJSONString(apiLog), e);
				}
			}
		});
	}

	public static void shutdown() {
		if (executorService != null) {
			executorService.shutdown();
			log.info("关闭异步发送日志线程池");
		}
	}

}
