package com.quantgroup.asset.distribution.service.distribute.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import com.quantgroup.asset.distribution.constant.DistributeConstants;
import com.quantgroup.asset.distribution.constant.RedisKeyConstants;
import com.quantgroup.asset.distribution.constant.StatusConstants;
import com.quantgroup.asset.distribution.enums.ExecuteType;
import com.quantgroup.asset.distribution.enums.funding.FundingResult;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.model.entity.DistributeRecord;
import com.quantgroup.asset.distribution.model.entity.fund.ChannelFundConfigNew;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.alarm.IAlarmService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRecordService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRuleConfigService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeService;
import com.quantgroup.asset.distribution.service.funding.IAidFundRouteService;
import com.quantgroup.asset.distribution.service.funding.IFundModuleChannelFundConfigNewService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRuleConfig;
import com.quantgroup.asset.distribution.service.jpa.entity.FundModuleChannelFundConfigNew;
import com.quantgroup.asset.distribution.service.notify.INotifyService;
import com.quantgroup.asset.distribution.service.product.IFinanceProductHitLogService;
import com.quantgroup.asset.distribution.service.product.IFinanceProductService;
import com.quantgroup.asset.distribution.service.redis.IRedisService;
import com.quantgroup.asset.distribution.service.rule.IRuleService;
import com.quantgroup.asset.distribution.service.rule.vo.IRuleVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 资产分发Service
 * @author liwenbin
 *
 */
@Slf4j
@Service
public class AssetDistributeServiceImpl implements IAssetDistributeService{
	
	/**
	 * 分发总开关
	 */
	@Value("${is.distribute}")
    private Boolean isDistribute;
	@Autowired
	private IAssetDistributeRuleConfigService assetDistributeRuleConfigService;
	@Autowired
	private IRedisService<Integer> redisService;
	@Autowired
	private IAidFundRouteService aidFundRouteService;
	@Autowired
	private INotifyService notifyService;
	@Autowired
	private IAssetDistributeRecordService assetDistributeRecordService;
	@Autowired
	private IAlarmService alarmService;
    @Autowired
    private IFundModuleChannelFundConfigNewService fundModuleChannelFundConfigNewService;
    @Autowired
    private IRuleService ruleService;
    @Autowired
    private IFinanceProductService financeProductService;
    @Autowired
    private IFinanceProductHitLogService financeProductHitLogService;

    private static final ExecutorService executorPool = Executors.newFixedThreadPool(4);
	
	/**
	 * 分发
	 */
	@Override
	public void distribute(AssetForm assetForm, Asset asset, Map<String, Object> data) {
		if (isDistribute) {
			List<AssetDistributeRuleConfig> assetDistributeRuleConfigList = assetDistributeRuleConfigService.getAllRuleConfigOrderByPriorityAsc();
			// 检查规则配置
			checkDistributeRuleConfig(assetDistributeRuleConfigList);
			// 获取该资产所有已经尝试分发过的类型
			Set<Integer> hasDistributedTypeSet = getAllDistributeRecordType(asset.getAssetNo());
			boolean success = false;
			DistributeRecord record = new DistributeRecord(data); 
			for (AssetDistributeRuleConfig ruleConfig : assetDistributeRuleConfigList) {
				Integer ruleType = ruleConfig.getAssetDistributeRuleType();
				// 如果配置被关闭或者该类型被分发过，就默认往下走，主要避免priority调换顺序的情况
				if (ruleConfig.getAssetDistributeRuleSwitch() == 0 || hasDistributedTypeSet.contains(ruleType)) { continue; } 
				String el = ruleConfig.getAssetDistributeRuleEl();
				boolean valid = ruleService.valid(el, data);
				record.addRecords(ruleConfig.getId(), el, ruleType, ruleConfig.getAssetDistributeRuleName(), valid);
				if (valid) {
					// 先创建记录，避免mq里消息来了就还没创建
					AssetDistributeRecord assetDistributeRecord = assetDistributeRecordService.saveDistributeRecord(asset, record, StatusConstants.WAIT, ruleType);
					int distributeStatus = beginDistribute(assetForm, asset, ruleType.intValue(), assetDistributeRecord, data);
					// 分发成功
					if (distributeStatus == StatusConstants.SUCCESS || distributeStatus == StatusConstants.WAIT) {
						log.info("用户执行分发节点, uuid : {}, bizNo : {}, assetNo : {}, bizChannel : {}, ruleId : {}, ruleName : {}, distributeStatus : {}", 
								assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), assetForm.getBizChannel(), ruleConfig.getId(), ruleConfig.getAssetDistributeRuleName(),
								distributeStatus);
						success = true;
					} else if (distributeStatus == StatusConstants.FAIL) {
						// 如果分发失败，直接更改状态
						assetDistributeRecord.setAssetDistributeStatus(StatusConstants.FAIL);
						assetDistributeRecordService.updateAssetDistribute(assetDistributeRecord);
					}
				}
				// 保存节点尝试记录
				saveAssetAttemptDistributeNode(asset.getAssetNo(), ruleType);
				if (success) { break; }
			}
			if (!success) {
				// 如果一个分配节点都没找到，报错
				throw new QGException(QGExceptionType.NO_DISTRIBUTE_NODE, asset.getUuid(), asset.getAssetNo(), JSON.toJSONString(record));
			}
		} else {
			log.info("资产分发开关关闭，直接通知资金系统, uuid : {}, bizNo : {}, assetNo : {}, bizType : {}, bizChannel : {}", 
					assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), assetForm.getBizType(), assetForm.getBizChannel());
			notifyService.notifyFundServer(assetForm, data);
		}
	}
	
	/**
	 * 开始进行分发
	 * @param assetForm
	 * @param ruleType
	 * @return 1:成功，2:失败， 3:分配中
	 */
	public int beginDistribute(AssetForm assetForm, Asset asset, int ruleType, AssetDistributeRecord assetDistributeRecord, Map<String, Object> data) {
		try {
			switch (ruleType) {
				case DistributeConstants.RuleType.FUND_ROUTE:
                    // 资方配置空跑,这里的目的是深度拷贝一个新对象，避免发生错乱
                    doTestExecute(JSON.parseObject(JSON.toJSONString(assetForm), AssetForm.class), data);
                    // 如果使用资方模块则去命中资方，创建金融产品集
                    String hitFinanceProduct = hitFundIfUseFundModule(assetForm, data, ExecuteType.ONLINE);
                    // 资方模块结果处理
                    financeProductService.checkFundResult(assetForm, hitFinanceProduct);
                    if(StringUtils.isNotEmpty(assetForm.getFinanceProducts())) {
                        notifyService.notifyFundServer(assetForm, data);
                        return StatusConstants.WAIT;
                    }
                    return StatusConstants.FAIL;
				case DistributeConstants.RuleType.AID_FUND_ROUTE: {
					boolean response = aidFundRouteService.aidFundRoute(assetForm, asset, data);
					return response ? StatusConstants.WAIT : StatusConstants.FAIL;
				}
				case DistributeConstants.RuleType.DIVERSION:
					notifyService.notifyFundServer(assetForm, data);
					return StatusConstants.WAIT;
				default:
					throw new QGException(QGExceptionType.UNKNOW_RULE_TYPE, ruleType);
			}
		} catch (Exception e) {
			// 如果报错，把保存的分发记录置为失效
			assetDistributeRecord.setEnable(false);
			assetDistributeRecordService.updateAssetDistribute(assetDistributeRecord);
			throw e;
		}
	}
	  

	/**
	 * 分发配置检查
	 * @param assetDistributeRuleConfigList
	 */
	public void checkDistributeRuleConfig(List<AssetDistributeRuleConfig> assetDistributeRuleConfigList) {
		int prePriority = -1;
		for (AssetDistributeRuleConfig assetDistributeRuleConfig : assetDistributeRuleConfigList) {
			if (assetDistributeRuleConfig.getAssetDistributeRulePriority().equals(prePriority)) {
				throw new QGException(QGExceptionType.DISTRIBUTE_RULE_CONFIG_PRIORITY_IS_NOT_UNIQUE);
			}
			prePriority = assetDistributeRuleConfig.getAssetDistributeRulePriority();
		}
	}
	
	/**
	 * 获取该用户所有分发记录Type,如果用户初次分发或者异常重试返回空
	 * @param assetNo
	 * @return
	 */
	public Set<Integer> getAllDistributeRecordType(String assetNo) {
		Set<Integer> sets = new HashSet<>();
		List<Integer> list = redisService.getList(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo);
		if (list == null || list.size() == 0) {
			// 如果缓存没有去查库，把库里执行过的都拿出来
			List<AssetDistributeRecord> assetDistributeRecords = assetDistributeRecordService.getDistributeRecord(assetNo);
			if (assetDistributeRecords == null || assetDistributeRecords.size() == 0) { return sets; }
			list = new ArrayList<>();
			for (AssetDistributeRecord assetDistributeRecord : assetDistributeRecords) {
				JSONArray array = JSON.parseObject(assetDistributeRecord.getAssetDistributeTravel()).getJSONArray("records");
				for (int i = 0, len = array.size(); i < len; ++i) {
					JSONObject record = array.getJSONObject(i);
					list.add(record.getInteger("type"));
				}
			}
		}
		sets.addAll(list);
		if (sets.size() != list.size()) {
			list = list.stream().distinct().collect(Collectors.toList());
			redisService.del(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo);
			redisService.setListEx(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo, list, 3, TimeUnit.DAYS);
		}
		return sets;
	}
	
	/**
	 * 保存资产尝试分发记录节点
	 */
	public void saveAssetAttemptDistributeNode(String assetNo, Integer ruleType) {
		redisService.rightPushEx(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo, ruleType, 3, TimeUnit.DAYS);
	}
	
	/**
	 * 接收fundingResult消息
	 */
	@Override
	public void receiveFundingResult(String bizNo, FundingResult fundingResult, String nextOperateDate) {
		try {
			Stopwatch stopwatch = Stopwatch.createStarted();
			// 1、更改分配记录状态
			if (fundingResult != FundingResult.HANG_UP) {
				// HANG_UP不更改,
				assetDistributeRecordService.updateAssetDistributeStatus(bizNo, fundingResult == FundingResult.FUAD_ASSIGN_SUCC ? StatusConstants.SUCCESS : StatusConstants.FAIL);
			}
			// 2、通知业务流系统
			notifyService.notifyBusinessFlow(bizNo, fundingResult, nextOperateDate);
			// 3、重新进行分发, 目前还没有，等接了助贷资金路由以后再增加
			log.info("资产分发接收资金结果处理结束, bizNo : {}, fundingResult : {}, 耗时 : {}", bizNo, fundingResult.getCode(), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
		} catch (QGException qe) {
			log.error("资产分发接收资金结果处理出现错误 : {}, bizNo : {}, fundingResult : {}", qe.qgExceptionType.code + "->" + qe.detail, bizNo, fundingResult.getCode()) ;
			alarmService.dingtalkAlarm("Warn", "资产分发接收资金结果处理出现错误", "bizNo : " + bizNo + " , fundingResult : " + fundingResult.getCode() + 
					" , 错误信息 : " + qe.qgExceptionType.code + "->" + qe.detail);
		} catch (Exception e) {
			log.error("资产分发接收资金结果处理异常, bizNo : {}, fundingResult : {}", bizNo, fundingResult.getCode(), e);
			alarmService.dingtalkAlarm("Warn", "资产分发接收资金结果处理出现异常", "bizNo : " + bizNo + " , fundingResult : " + fundingResult.getCode() + 
					" , 错误信息 : 未知错误");
		}
	}



    /**
     * 如果使用资方模块，需要去命中资方
     * @param assetForm
     */
    public String hitFundIfUseFundModule(AssetForm assetForm, Map<String, Object> data, ExecuteType executeType) {
        // 如果auditResult为true， amount或term有一个为空， 那就不管是否是测试，都不能使用资方模块了
        // 同时兼容量子魔方白名单，白名单的没有期数直接返回
        if ("true".equals(assetForm.getAuditResult()) && (StringUtils.isEmpty(assetForm.getAmount()) || StringUtils.isEmpty(assetForm.getTerm()))) {
            return null;
        }

        FundModuleChannelFundConfigNew config = fundModuleChannelFundConfigNewService.findByBizChannel(assetForm.getBizChannel());
        // 未找到资方渠道配置直接通知
        if (config == null) {
            return null;
        }
        JSONArray fundArray = new JSONArray();
        // 资方去重, 可能存在多条件同一个资方
        Set<String> fundSet = new HashSet<>();
        List<ChannelFundConfigNew> fundConfigList = JSONArray.parseArray(config.getFunds(), ChannelFundConfigNew.class);
        A : for (ChannelFundConfigNew channelFundConfig : fundConfigList) {
            IRuleVO ruleVO = ruleService.getIRuleVo(channelFundConfig.getLimits());
            if (ruleVO == null) { throw new QGException(QGExceptionType.CRATE_RULE_VO_ERROR); }
            // 如果参数为空，直接不满足
            Set<String> params = ruleVO.getParamNames();
            for (String key : params) {
                if (!data.containsKey(key)) {
                    continue A;
                }
            }
            if (!ruleVO.valid(data)) {
                continue A;
            }
            // 是否配了审核条件
            boolean hasAuditResultLimit = params.contains("audit_result");
            if (!hasAuditResultLimit) {
                // 如果没配，自动过一层auditResult@true的条件
                if (!ruleService.valid("audit_result==true", data)) {
                    continue A;
                }
            }
            String key = channelFundConfig.getFundId() + "_" + channelFundConfig.getFundProductId() + "_" + channelFundConfig.getPriority();
            if (!fundSet.contains(key)) {
                // 创建并增加资方配置
                JSONObject fundInfoJSON = new JSONObject();
                fundInfoJSON.put("fundId", channelFundConfig.getFundId());
                fundInfoJSON.put("fundProductId", channelFundConfig.getFundProductId());
                fundInfoJSON.put("priority", channelFundConfig.getPriority());
                fundInfoJSON.put("feeType", channelFundConfig.getFeeType());
                fundInfoJSON.put("rateType", channelFundConfig.getRateType());
                fundInfoJSON.put("rate", channelFundConfig.getRate());
                if (StringUtils.isNotEmpty(assetForm.getFundRate())) { fundInfoJSON.put("fundRate", assetForm.getFundRate()); }

                fundArray.add(fundInfoJSON);
                log.info("资方模块用户命中资方条件, uuid : {}, assetNo : {}, bizNo : {}, bizChannel : {}, fundId : {}, fundProductId : {}",
                        assetForm.getUuid(), assetForm.getAssetNo(), assetForm.getBizNo(), assetForm.getBizChannel(), channelFundConfig.getFundId(), channelFundConfig.getFundProductId());
                fundSet.add(key);
            }
        }

        // 如果审核拒绝，也没命中任何资方, 直接返回
        if ("false".equals(assetForm.getAuditResult()) && fundArray.size() == 0) {
            return null;
        }
        QGPreconditions.checkArgument(fundArray.size() != 0, QGExceptionType.NO_FUND_INFO_BEEN_HIT, assetForm.getBizChannel(), assetForm.getAmount(), assetForm.getTerm());
        // 看命中优先级是否符合要求
        boolean[] bucket = new boolean[fundArray.size() + 1];
        for (int i = 0, len = fundArray.size(); i < len; i++) {
            int priority = fundArray.getJSONObject(i).getIntValue("priority");
            if (!(priority > 0 && priority <= len)) {
                throw new QGException(QGExceptionType.FUND_PRIORITY_IS_ERROR, assetForm.getBizChannel(), assetForm.getAmount(), assetForm.getTerm());
            }
            if (bucket[priority]) {
                // 多个相同的优先级
                throw new QGException(QGExceptionType.FUND_PRIORITY_IS_ERROR, assetForm.getBizChannel(), assetForm.getAmount(), assetForm.getTerm());
            }
            bucket[priority] = true;
        }
        String hitFinanceProduct = createFinancePro(assetForm, fundArray);
        log.info("资方模块组成金融产品集完成, uuid : {}, assetNo : {}, bizNo : {}, bizChannel : {}, financeProduct : {}, executeType : {}", assetForm.getUuid(), assetForm.getAssetNo(), assetForm.getBizNo(), assetForm.getBizChannel(), hitFinanceProduct, executeType.name());
        return hitFinanceProduct;
    }

    /**
     * 组建金融产品集
     * @param assetForm
     * @param fundArray
     * @return
     */
    private String createFinancePro(AssetForm assetForm, JSONArray fundArray) {
        // 首先判断是否有额度期数
        QGPreconditions.checkArgument(StringUtils.isNotEmpty(assetForm.getAmount()) && StringUtils.isNotEmpty(assetForm.getTerm()), QGExceptionType.HIT_FUND_BUT_AMOUNT_OR_TERM_IS_EMPTY);

        JSONArray financeProductArray = new JSONArray();
        JSONObject amountJSON = new JSONObject();
        financeProductArray.add(amountJSON);
        amountJSON.put("min", assetForm.getAmount());
        amountJSON.put("max", assetForm.getAmount());
        JSONArray termArray = new JSONArray();
        amountJSON.put("terms", termArray);
        JSONObject termJSON = new JSONObject();
        termArray.add(termJSON);
        try {
            // 不能是string，否则资方那边会报错
            termJSON.put("term", Integer.parseInt(assetForm.getTerm()));
        } catch (Exception e) {
            termJSON.put("term", Double.parseDouble(assetForm.getTerm()));
        }
        termJSON.put("fundInfo", fundArray);

        return JSON.toJSONString(financeProductArray);
    }

    /**
     * 资方配置空跑
     * @param assetForm
     * @param data
     */
    private void doTestExecute(AssetForm assetForm, Map<String, Object> data) {
        executorPool.execute(() -> {
            try {
                assetForm.setBizChannel("88888_" + assetForm.getBizChannel());
                String hitFundPro = hitFundIfUseFundModule(assetForm, data, ExecuteType.TEST);
                if (hitFundPro != null) {
                    // 如果命中了记录一下，并且如果审核状态为false改为true
                    String oldFundPro = assetForm.getFinanceProducts();
                    String oldAuditResult = assetForm.getAuditResult();
                    assetForm.setFinanceProducts(hitFundPro);
                    assetForm.setAuditResult("true");
                    financeProductHitLogService.saveLog(assetForm, oldFundPro, hitFundPro, oldAuditResult, assetForm.getAuditResult(), ExecuteType.TEST);
                } else {
                    // 如果没命中,且审核结果未false
                    if ("false".equals(assetForm.getAuditResult())) {
                        // 把传过来的额度和期数处理为null
                        assetForm.setAmount(null);
                        assetForm.setTerm(null);
                    }
                }
                log.info("空跑审核最终结果, assetForm : {}", JSON.toJSONString(assetForm));
            }  catch (QGException qe) {
                log.error("资方配置执行空跑出现错误 : {}, uuid : {}, bizChannel : {}, bizType : {}, bizNo : {}, assetNo : {} ",
                        qe.qgExceptionType.code + "->" + qe.detail, assetForm.getUuid(),
                        assetForm.getBizChannel(), assetForm.getBizType(),
                        assetForm.getBizNo(), assetForm.getAssetNo());
                alarmService.dingtalkAlarm("Warn", "资方配置执行空跑出现错误", "bizChannel : " + assetForm.getBizChannel()
                        + " , bizType : " + assetForm.getBizType() + " , bizNo : " + assetForm.getBizNo()
                        + " , assetNo : " + assetForm.getAssetNo() + " , uuid : " + assetForm.getUuid()
                        + " , 错误信息 : " + qe.qgExceptionType.code + "->" + qe.detail);
            } catch (Exception ex) {
                log.error("资方配置执行空跑出现异常, uuid : {}, bizChannel : {}, bizType : {}, bizNo : {}, assetNo : {} ", assetForm.getUuid(),
                        assetForm.getBizChannel(), assetForm.getBizType(),
                        assetForm.getBizNo(), assetForm.getAssetNo(), ex);
                alarmService.dingtalkAlarm("Warn", "资方配置执行空跑出现异常", "bizChannel : " + assetForm.getBizChannel()
                        + " , bizType : " + assetForm.getBizType() + " , bizNo : " + assetForm.getBizNo()
                        + " , assetNo : " + assetForm.getAssetNo() + " , uuid : " + assetForm.getUuid()
                        + " , 错误信息 : 未知错误.");
            }
        });
    }
}
