package com.elab.spring.callback.impl;

import com.alibaba.fastjson.JSON;
import com.dianping.cat.Cat;
import com.elab.spring.callback.IRestFallback;
import com.elab.spring.dao.MngHttpFailureDataDao;
import com.elab.spring.dao.MngHttpInfoDao;
import com.elab.spring.dao.entity.MngHttpFailureDataEntity;
import com.elab.spring.dao.entity.MngHttpInfoEntity;
import com.elab.spring.exception.rest.CoolingException;
import com.elab.spring.model.rest.HttpUrlMetric;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpHeaders;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

/**
 * 默认的失败回调
 *
 * @author ： liukx
 * @time ： 2020/6/17 - 14:12
 */
public class DefaultRestFallBack implements IRestFallback, ApplicationContextAware, InitializingBean {


    @Value("${spring.application.name}")
    private String applicationName;

    private Logger logger = LoggerFactory.getLogger(DefaultRestFallBack.class);

    private ApplicationContext applicationContext;

    private MngHttpFailureDataDao httpFailureDataDao;

    private MngHttpInfoDao httpInfoDao;
    /**
     * 各部分组件是否加载完毕
     */
    private boolean isReload = false;
    /**
     * 冷却功能是否开启
     */
    private boolean isEnableCoolingTime = false;
    /**
     * 是否记录日志表
     */
    private boolean isInsertLog = true;

    private Map<String, HttpUrlMetric> httpUrlMetricMap = new ConcurrentHashMap<>();

    private Integer failStatus = -1;

    private Integer okStatus = 1;

    private static Object lock = new Object();

    private List<String> errorMsgList = Arrays.asList("Connection refused", "404 Not Found", "502 Bad Gateway");

    public void setEnableCoolingTime(boolean enableCoolingTime) {
        isEnableCoolingTime = enableCoolingTime;
    }

    public void setInsertLog(boolean insertLog) {
        isInsertLog = insertLog;
    }

    /**
     * 新增异常消息匹配
     *
     * @param msg
     */
    public void addErrorMsg(String msg) {
        errorMsgList.add(msg);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.applicationContext != null) {
            this.httpFailureDataDao = this.applicationContext.getBean(MngHttpFailureDataDao.class);
            this.httpInfoDao = this.applicationContext.getBean(MngHttpInfoDao.class);
            if (this.httpFailureDataDao != null && this.httpInfoDao != null) {
                logger.debug("加载完成http故障持久层对象");
                isReload = true;
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
//    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public Object post(String url, HttpHeaders httpHeaders, Object reqParam, Class clazz, Exception e) {
        if (this.isReload) {
            try {
                CoolingProcess(url, e);
                invokeInsert(url, "POST", httpHeaders, reqParam, e);
            } catch (Exception e1) {
                logger.error("补偿失败", e1);
                e1.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 冷却逻辑计算
     *
     * @param url
     * @param e
     * @throws Exception
     */
    private void CoolingProcess(String url, Exception e) throws Exception {
        if (isEnableCoolingTime) {
            HttpUrlMetric httpInfo = getHttpInfo(url);
            logger.debug("异常信息:" + e.getMessage());
            // 连接异常直接进入冷却
            if (errorMsgList.contains(e.getMessage())) {
                httpInfo.setReqStatus(failStatus);
                Integer recoveryRetryCount = httpInfo.getRecoveryRetryCount() == null ? 1 : httpInfo
                        .getRecoveryRetryCount() + 1;
                long nextTime = System.currentTimeMillis() + (recoveryRetryCount * 60000);
                Date nextRetryTime = new Date(nextTime);
                updateHttpInfoReqStatus(url, httpInfo.getId(), failStatus, recoveryRetryCount, nextRetryTime);
            } else if (e instanceof CoolingException) {
                logger.debug("冷却异常不做计算");
            } else {
                triggerErrorCount(httpInfo);
            }
        }
    }

    /**
     * 触发异常总数计算
     *
     * @param httpInfo
     * @throws Exception
     */
    private void triggerErrorCount(HttpUrlMetric httpInfo) throws Exception {
        httpInfo.getFailCount().increment();
        triggerCount(httpInfo, httpInfo.getFailCount(), failStatus);
    }

    /**
     * 触发调用成功计算
     *
     * @param httpInfo
     * @throws Exception
     */
    private void triggerOKCount(HttpUrlMetric httpInfo) throws Exception {
        LongAdder failCount = httpInfo.getFailCount();
        if (failCount.intValue() > 0) {
            // 自增
            httpInfo.getOkCount().increment();
        }
        triggerCount(httpInfo, httpInfo.getOkCount(), okStatus);
    }

    /**
     * 核心计算总数
     *
     * @param httpInfo
     * @param count
     * @param status
     * @throws Exception
     */
    private void triggerCount(HttpUrlMetric httpInfo, LongAdder count, Integer status) throws Exception {
        int criticalValue = httpInfo.getSimpleCount() / 2;
        // 如果达到临界值
        if (count.intValue() >= criticalValue) {
            Integer id = httpInfo.getId();
            Date nextRetryTime = null;
            Integer recoveryRetryCount = httpInfo.getRecoveryRetryCount() + 1;
            if (failStatus.equals(status)) {
                long longTime = System.currentTimeMillis() + recoveryRetryCount * 60000;
                nextRetryTime = new Date(longTime);
            }
            updateHttpInfoReqStatus(httpInfo.getUrl(), id, status, recoveryRetryCount, nextRetryTime);
            // 重新计算
            httpInfo = null;
        }
    }

    private void invokeInsert(String url, String method, HttpHeaders httpHeaders, Object reqParam, Exception e) {
        if (isInsertLog) {
            try {
                MngHttpFailureDataEntity httpFailureDataEntity = new MngHttpFailureDataEntity();
                httpFailureDataEntity.setCatId(Cat.getCurrentMessageId());
                httpFailureDataEntity.setCreated(new Date());
                httpFailureDataEntity.setCreator(getClass().getSimpleName());
                httpFailureDataEntity.setUrl(url);
                httpFailureDataEntity.setReqStatus(-1);
                httpFailureDataEntity.setReqBody(JSON.toJSONString(reqParam));
                httpFailureDataEntity.setReqMethod(method);
                httpFailureDataEntity.setHeaderBody(JSON.toJSONString(httpHeaders));
                httpFailureDataEntity.setStatus(okStatus);
                httpFailureDataEntity.setErrorMsg(e.getMessage());
                httpFailureDataEntity.setReqRetry(0);
                httpFailureDataEntity.setProjectName(applicationName);
                int result = this.httpFailureDataDao.insert(httpFailureDataEntity);
                logger.debug("记录失败日志成功 日志编号:" + result);
            } catch (Exception e1) {
                logger.error("故障存储数据异常", e1);
                e1.printStackTrace();
            }
        }
    }

    @Override
//    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public Object get(String url, Class clazz, Exception e) {
        if (this.httpFailureDataDao != null) {
            try {
                CoolingProcess(url, e);
                invokeInsert(url, "GET", null, null, e);
            } catch (Exception e1) {
                logger.error("补偿失败", e1);
                e1.printStackTrace();
            }
        }
        return null;
    }


    @Override
    //@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void requestBefore(String url, HttpHeaders httpHeaders, Object reqParam, Class clazz) throws Exception {
        if (isReload && isEnableCoolingTime) {
            // 获取http的配置信息
            MngHttpInfoEntity httpInfo = getHttpInfo(url);
            Date recoveryTime = httpInfo.getRecoveryTime();
            long currentTime = System.currentTimeMillis();
            if (failStatus.equals(httpInfo.getReqStatus())) {
                if ((recoveryTime != null) && (recoveryTime.getTime() > currentTime)) {
                    // 处于冷却时间
                    throw new CoolingException(url + "处于冷却中");
                }
            }
        }
    }

    /**
     * 获取http详情的信息
     * 这里希望访问正常的请求走本地缓存，如果是非正常的全部通过数据库读取
     *
     * @param url
     * @return
     * @throws Exception
     */
    private HttpUrlMetric getHttpInfo(String url) throws Exception {

        HttpUrlMetric httpUrlMetric = httpUrlMetricMap.get(url);
        // 只有在有效的时候才返回
        if (httpUrlMetric != null) {
            if (okStatus.equals(httpUrlMetric.getReqStatus())) {
                return httpUrlMetric;
            } else {
                logger.debug("非正常状态，需要重新从数据库获取");
            }
        }
        synchronized (lock) {
            MngHttpInfoEntity searchEntity = new HttpUrlMetric();
            searchEntity.setProjectName(this.applicationName);
            searchEntity.setUrl(url);
            List<MngHttpInfoEntity> httpInfoEntities = this.httpInfoDao.selectByList(searchEntity);

            if (httpInfoEntities.size() > 0) {
                MngHttpInfoEntity httpInfoEntity = httpInfoEntities.get(httpInfoEntities.size() - 1);
                httpUrlMetric = new HttpUrlMetric();
                BeanUtils.copyProperties(httpInfoEntity, httpUrlMetric);
                // 只有在有效的时候进行缓存
                if (okStatus.equals(httpInfoEntity.getReqStatus())) {
                    httpUrlMetricMap.put(url, httpUrlMetric);
                }
            } else {
                searchEntity.setCatId(Cat.getCurrentMessageId());
                searchEntity.setReqStatus(okStatus);
                searchEntity.setSimpleCount(20);
                searchEntity.setRecoveryRetryCount(0);
                searchEntity.setStatus(1);
                searchEntity.setCreated(new Date());
                int result = this.httpInfoDao.insert(searchEntity);
                searchEntity.setId(result);
                logger.debug("初始化MngHttpInfoEntity数据 : " + result);
                httpUrlMetric = new HttpUrlMetric();
                BeanUtils.copyProperties(searchEntity, httpInfoEntities);
                httpUrlMetricMap.put(url, httpUrlMetric);
            }
            return httpUrlMetric;
        }
    }


    @Override
    //@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void requestAfter(String url, HttpHeaders httpHeaders, Object reqParam, Class clazz) throws Exception {
        if (isEnableCoolingTime) {
            HttpUrlMetric httpInfo = getHttpInfo(url);
            triggerOKCount(httpInfo);
        }
    }

    private void updateHttpInfoReqStatus(String url, Integer id, Integer status, Integer recoveryRetryCount, Date recoveryTime) throws Exception {
        MngHttpInfoEntity updateInfo = new HttpUrlMetric();
        updateInfo.setId(id);
        updateInfo.setUpdated(new Date());
        updateInfo.setReqStatus(status);
        updateInfo.setRecoveryRetryCount(recoveryRetryCount);
        updateInfo.setRecoveryTime(recoveryTime);
        httpInfoDao.updateById(updateInfo);
        logger.debug("URL : " + url + " 恢复到[" + status + "]状态");
    }
}
