package com.quantgroup.asset.distribution.service.newrule;

import cn.quantgroup.motan.bean.UserAssociationBean;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import com.quantgroup.asset.distribution.constant.AssetAttributeConstants;
import com.quantgroup.asset.distribution.constant.CommonConstants;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.alarm.IAlarmService;
import com.quantgroup.asset.distribution.service.alarm.impl.MarkdownMessage;
import com.quantgroup.asset.distribution.service.distribute.IDistributeFailLogService;
import com.quantgroup.asset.distribution.service.feature.IFeatureService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import com.quantgroup.asset.distribution.service.jpa.entity.*;
import com.quantgroup.asset.distribution.service.jpa.repository.IChannelRuleRepository;
import com.quantgroup.asset.distribution.service.jpa.repository.IFundProductRepository;
import com.quantgroup.asset.distribution.service.jpa.repository.IRoutingRecordRepository;
import com.quantgroup.asset.distribution.service.jpa.repository.IWhiteListRepository;
import com.quantgroup.asset.distribution.service.newrule.pojo.RoutingRecordVO;
import com.quantgroup.asset.distribution.service.newrule.pojo.RuleConstant;
import com.quantgroup.asset.distribution.service.newrule.pojo.enums.AreaEnum;
import com.quantgroup.asset.distribution.service.newrule.pojo.enums.CooperationEnum;
import com.quantgroup.asset.distribution.service.newrule.pojo.enums.IdCardExpireEnum;
import com.quantgroup.asset.distribution.service.newrule.pojo.funds.*;
import com.quantgroup.asset.distribution.service.newrule.service.IProductRuleService;
import com.quantgroup.asset.distribution.service.newrule.third.AuditResponce;
import com.quantgroup.asset.distribution.service.user.IUserCenterService;
import com.quantgroup.asset.distribution.util.IdCardUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import shaded.com.google.common.collect.Lists;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.IntStream;

/**
 * @author shihuajun
 * @date 2021/8/27 14:12
 * @ describing
 */
@Slf4j
@Service
public class CoreFilter {

    private static final String AUDIT_FAIL_TEXT = "通知业务系统审核结果失败，uuid 是 %s";

    @Resource
    private IHttpService httpService;
    @Resource
    private IAlarmService alarmService;
    @Resource
    private RuleHandleFactory ruleHandleFactory;
    @Resource
    private IProductRuleService productRuleService;
    @Resource
    private IFundProductRepository fundProductRepository;
    @Resource
    private IWhiteListRepository whiteListRepository;
    @Resource
    private IUserCenterService userCenterService;
    @Resource
    private IChannelRuleRepository channelRuleRepository;
    @Resource
    private IRoutingRecordRepository routingRecordRepository;

    @Autowired
    private IDistributeFailLogService distributeFailLogService;

    @Autowired
    private IFeatureService featureService;

    @Async
    @Transactional(rollbackFor = Exception.class)
    public void coreHandle(AuditResponce auditResponce) {
        try {
            List<ChannelRuleEntity> channelRuleEntityList = new ArrayList<>();
            FilterResult result = new FilterResult();
            List<FinanceProduct> financeProducts = new ArrayList<>();
            List<RoutingRecordVO> routingRecordList = new ArrayList<>();
            UserAssociationBean associationBean = userCenterService.getUserAssociationBean(auditResponce.getUuid());
            //白名单
            WhiteListEntity whiteListEntity = whiteListRepository.getByPhoneEquals(associationBean.getPhoneNo());
            if (whiteListEntity != null && whiteListEntity.getStatus().equals(Byte.valueOf("1"))) {
                if (auditResponce.getAuditResult()) {
                    ChannelRuleEntity channelRuleEntity = buildFakeChannelRule(whiteListEntity, auditResponce);
                    channelRuleEntityList.add(channelRuleEntity);
                }
            } else {
                channelRuleEntityList = channelRuleRepository
                    .getByChannelIdAndCreditResultOrderByPriority(Long.parseLong(auditResponce.getBizChannel()),
                        auditResponce.getAuditResult() ? 1 : 0);
            }
            if (CollectionUtils.isEmpty(channelRuleEntityList)) {
                log.error("渠道配置产品集不存在，运营人员配置有问题, bizNo:{}", auditResponce.getBizNo());
            } else {
                for (ChannelRuleEntity channelRuleEntity : channelRuleEntityList) {
                    if (channelRuleEntity.getPublishStatus().equals(0)) {
                        continue;
                    }
                    FundProductEntity fundProduct = fundProductRepository.getByIdEquals(channelRuleEntity.getFundProductId());
                    log.info("coreHandle | 开始路由channelRuleEntity={}", channelRuleEntity.toString());
                    if (fundProduct == null) {
                        log.error("这种情况不可能出现，需要看下原因 {}", JSON.toJSONString(channelRuleEntity));
                        continue;
                    }
                    if (currentVerify(fundProduct, auditResponce, associationBean, channelRuleEntity)) {
                        log.info("coreHandle | 所有规则校验通过");
                        financeProducts.add(build(auditResponce, fundProduct, channelRuleEntity));
                        routingRecordList.add(generateRouteRecord(channelRuleEntity));
                    }
                }
            }
            if (CollectionUtils.isEmpty(financeProducts)) {
                log.error("筛选完成后没有可用的配置, bizNo:{}", auditResponce.getBizNo());
                auditResponce.setAuditResult(false);
            } else {
                result.setFinanceProducts(JSON.toJSONString(financeProducts));
                auditResponce.setAuditResult(true);
            }
            log.info("coreHandle | 通知业务系统审核结果");
            Map<String, String> response = httpService.postHasResponse(auditResponce.getCallbackUrl(), result.get(auditResponce));
            if (MapUtils.isEmpty(response) || !(HttpStatus.OK.value() + "").equals(response.get("statusCode"))
                    || (!"success".equals(response.get("response")) && (!JSONObject.isValidObject(response.get("response"))
                    || 0 != JSONObject.parseObject(response.get("response")).getInteger("code").intValue()))) {
                MarkdownMessage markdownMessage = new MarkdownMessage();
                markdownMessage.setTitle("通知业务系统审核结果失败告警");
                markdownMessage.add(String.format(AUDIT_FAIL_TEXT, auditResponce.getUuid()));
                alarmService.sendMsgWithAlert(markdownMessage);
                log.error("通知业务系统审核结果失败 {}", JSON.toJSONString(auditResponce));
                throw new QGException(QGExceptionType.NOTIFY_BIZ_ERROR);
            } else {
                log.info("coreHandle | 保存路由记录");
                RoutingRecordEntity routingRecordEntity = new RoutingRecordEntity();
                routingRecordEntity.setCreditNo(auditResponce.getBizNo());
                routingRecordEntity.setPhone(associationBean.getPhoneNo());
                routingRecordEntity.setUuid(auditResponce.getUuid());
                if (CollectionUtils.isEmpty(financeProducts)) {
                    routingRecordEntity.setStatus(new Byte("0"));
                    routingRecordEntity.setRoutedResult("");
                } else {
                    routingRecordEntity.setStatus(new Byte("1"));
                    routingRecordEntity.setRoutedResult(JSON.toJSONString(routingRecordList));
                }
                routingRecordRepository.save(routingRecordEntity);
            }
        } catch (Exception e) {
            log.error("资金路由分发出现异常, uuid : {}, bizChannel : {}, bizType : {}, bizNo : {} e:{}", auditResponce.getUuid(),
                    auditResponce.getBizChannel(), auditResponce.getBizType(), auditResponce.getBizNo(), ExceptionUtils.getStackTrace(e));
            distributeFailLogService.saveDistributeFailLogNew(auditResponce, ExceptionUtils.getMessage(e));
            alarmService.dingtalkAlarm("Warn", "资金路由分发出现异常", "bizChannel : " + auditResponce.getBizChannel()
                    + " , bizType : " + auditResponce.getBizType() + " , bizNo : " + auditResponce.getBizNo()
                    + " , uuid : " + auditResponce.getUuid()
                    + " , 错误信息 : " + ExceptionUtils.getMessage(e));
        }
    }

    private ChannelRuleEntity buildFakeChannelRule(WhiteListEntity white, AuditResponce auditResult) {
        ChannelRuleEntity ruleEntity = new ChannelRuleEntity();
        ruleEntity.setEnable(true);
        ruleEntity.setPublishStatus(1);
        ruleEntity.setFundProductId(white.getFundProductId());
        ruleEntity.setBusinessType(white.getBusinessType());
        ruleEntity.setChannelId(Long.valueOf(auditResult.getBizChannel()));
        ruleEntity.setCreditResult(1);
        ruleEntity.setFundId(white.getFundId());
        ruleEntity.setFundName(white.getFundName());
        ruleEntity.setFundProId(white.getFundProId());
        ruleEntity.setId(-9999L);
        ruleEntity.setOrgType(white.getOrgType());
        ruleEntity.setPriority(1);
        ruleEntity.setRouteWait(false);
        ruleEntity.setFundType(white.getFundType());
        return ruleEntity;
    }

    private RoutingRecordVO generateRouteRecord(ChannelRuleEntity channelRuleEntity) {
        RoutingRecordVO routingRecordVO = new RoutingRecordVO();
        BeanUtils.copyProperties(channelRuleEntity, routingRecordVO);
        return routingRecordVO;
    }

    private FinanceProduct build(AuditResponce auditResponce, FundProductEntity fundProduct, ChannelRuleEntity channelRuleEntity) {
        FundInfo fundInfo = new FundInfo();
        fundInfo.setFundId(fundProduct.getFundId());
        fundInfo.setFundProductId(fundProduct.getFundProId() + "");
        fundInfo.setCooperation(CooperationEnum.parse(fundProduct.getOrgType()).ordinal());
        fundInfo.setWaitFlg(channelRuleEntity.getRouteWait());
        Terms term = new Terms();
        term.setTerm(Integer.parseInt(auditResponce.getTerm()));
        term.setFundInfos(Lists.newArrayList(fundInfo));
        FinanceProduct financeProduct = new FinanceProduct();
        financeProduct.setTerms(Lists.newArrayList(term));
        financeProduct.setMax(new BigDecimal(auditResponce.getAmount()));
        financeProduct.setMin(new BigDecimal(auditResponce.getAmount()));
        financeProduct.setWaitFlg(channelRuleEntity.getRouteWait());
        log.info("构造资金产品集 financeProduct:{}", JSONObject.toJSONString(financeProduct));
        return financeProduct;
    }

    private Boolean currentVerify(FundProductEntity fundProduct, AuditResponce auditResponce,
                                  UserAssociationBean associationBean, ChannelRuleEntity channelRuleEntity) {
        if (fundProduct.getEnable().equals(new Byte("0"))) {
            return Boolean.FALSE;
        }
        WhiteListEntity whiteListEntity = whiteListRepository.getByPhoneEqualsAndStatusEqualsAndFundProIdEquals(associationBean.getPhoneNo(),
            new Byte("1"), fundProduct.getFundProId());
        if (whiteListEntity != null) {
            log.info("白名单用户，phoneNo:{}", associationBean.getPhoneNo());
        }
        Boolean basicInfoVerify = basicInfoVerify(fundProduct, associationBean, auditResponce, channelRuleEntity);
        if (whiteListEntity != null && basicInfoVerify) {
            log.info("currentVerify | 走白名单");
            return Boolean.TRUE;
        }
        return specialInfoVerify(fundProduct, associationBean) && basicInfoVerify;
    }

    private Boolean basicInfoVerify(FundProductEntity fundProduct, UserAssociationBean associationBean,
                                    AuditResponce auditResponce, ChannelRuleEntity channelRuleEntity) {
        if (StringUtils.isBlank(fundProduct.getBasicRule())) {
            log.error("后台基础配置出问题了，资方产品信息是 {}", JSON.toJSONString(fundProduct));
            throw new RuntimeException("后台基础配置出问题了");
        }
        long channelId = channelRuleEntity.getChannelId();
        if (channelId != Long.parseLong(auditResponce.getBizChannel())) {
            log.error("channelId不符");
            return Boolean.FALSE;
        }

        //如果配置不为空，则匹配参数是否符合
        if (StringUtils.isNotBlank(channelRuleEntity.getUserLevel())
            && (StringUtils.isEmpty(auditResponce.getCreditLevel()) || !channelRuleEntity.getUserLevel().contains(auditResponce.getCreditLevel()))) {
            log.error("CreditLevel不符 userLevel:{}", auditResponce.getCreditLevel());
            return Boolean.FALSE;
        }
        String ruleUserTag = channelRuleEntity.getUserTag();
        if (StringUtils.isNotBlank(ruleUserTag)) {
            Set<String> keys = new HashSet<>();
            keys.add(AssetAttributeConstants.USER_LOAN_TYPE_V4);
            AssetForm assetForm = new AssetForm();
            assetForm.setUuid(auditResponce.getUuid());
            assetForm.setBizChannel(auditResponce.getBizChannel());
            assetForm.setBizNo(auditResponce.getBizNo());
            assetForm.setBizType(auditResponce.getBizType());
            assetForm.setExData(auditResponce.getExData());
            Map<String, Object> data = featureService.getFeatureData(keys, assetForm, 1);
            if (Objects.isNull(data) || Objects.isNull(data.get(AssetAttributeConstants.USER_LOAN_TYPE_V4))) {
                log.error("查询特征userTag为空.查询入参:{}",assetForm);
                return Boolean.FALSE;
            }
            String userTag = String.valueOf(data.get(AssetAttributeConstants.USER_LOAN_TYPE_V4));
            if (!ruleUserTag.contains(userTag)) {
                log.error("UserTag不符, 特征userTag:{}，配置userTag:{}", userTag, ruleUserTag);
                return Boolean.FALSE;
            }
        }
        String basicRule = fundProduct.getBasicRule();
        Expression compiledExp = AviatorEvaluator.compile(basicRule);
        List<ProductRuleEntity> ruleEntityList = productRuleService.getAll();
        Map<String, Object> env = Maps.newConcurrentMap();
        if (StringUtils.isNotBlank(fundProduct.getTelRule())) {
//            env.put(RuleConstant.NOT_PERMIT_TEL, Lists.newArrayList(Arrays.stream(fundProduct.getTelRule()
//                    .split(CommonConstants.COMMA)).mapToInt(s -> Integer.parseInt(s))));
            env.put(RuleConstant.NOT_PERMIT_TEL, JSONArray.parseArray(fundProduct.getTelRule()).toJavaList(String.class));
        }
        for (ProductRuleEntity pre : ruleEntityList) {
            if (pre.getEnable().equals(new Byte("0"))) {
                continue;
            }
            String currentRuleVal = pre.getRuleVal();
            if (!basicRule.contains(currentRuleVal)) {
                continue;
            }
            if (currentRuleVal.equals(RuleConstant.AMOUNT)) {
                env.put(currentRuleVal, new BigDecimal(auditResponce.getAmount()));
            }
            if (currentRuleVal.equals(RuleConstant.TERM)) {
                env.put(currentRuleVal, Integer.parseInt(auditResponce.getTerm()));
            }
            int age = IdCardUtil.getAge(associationBean.getIdNo());
            env.put(RuleConstant.AGE, age);
            if (currentRuleVal.equals(RuleConstant.NOT_PERMIT_TEL)) {
                env.put(RuleConstant.NOT_PERMIT_TELS, Integer.parseInt(StringUtils.substring(associationBean.getPhoneNo(), 0, 3)));
            }
        }
        Boolean execute = (Boolean) compiledExp.execute(env);
        log.info("基础校验结果 {}", execute);
        if (execute) {
            execute = commonVerify(fundProduct, associationBean, auditResponce);
        }
        log.info("公共校验结果 {}", execute);
        return execute;
    }

    private Boolean commonVerify(FundProductEntity fundProduct, UserAssociationBean associationBean, AuditResponce auditResponce) {
        for (IRuleVerifyService ruleService : RuleHandleFactory.COMMON_VERIFY) {
            RuleParam ruleParam = new RuleParam();
            ruleParam.setUserInfo(associationBean);
            ruleParam.setAreaNum(AreaEnum.parse(fundProduct.getAreaTerm()));
            ruleParam.setArea(fundProduct.getAreaRule());
            ruleParam.setUuid(auditResponce.getUuid());
            ruleParam.setCardExpireEnum(IdCardExpireEnum.parse(fundProduct.getCardLimit()));
            if (!ruleService.pass(ruleParam)) {
                log.info("当前用户公共校验不通过 {}", JSON.toJSONString(associationBean));
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    private Boolean specialInfoVerify(FundProductEntity fundProduct, UserAssociationBean associationBean) {
        String ruleList = fundProduct.getRuleList();
        if (StringUtils.isBlank(ruleList)) {
            return Boolean.TRUE;
        }
        String[] rules = ruleList.split(CommonConstants.COMMA);
        for (String id : rules) {
            ProductRuleEntity productRuleEntity = productRuleService.getProductRuleEntityById(Long.parseLong(id));
            IRuleVerifyService ruleVerifyService = ruleHandleFactory.getRuleVerifyServiceMap(productRuleEntity.getRuleEngine());
            RuleParam ruleParam = new RuleParam();
            ruleParam.setUserInfo(associationBean);
            if (!ruleVerifyService.pass(ruleParam)) {
                log.info("当前用户其他规则检验不通过 {}", JSON.toJSONString(associationBean));
                return Boolean.FALSE;
            }
        }
        log.info("specialInfoVerify | 当前用户其他规则检验通过");
        return Boolean.TRUE;
    }

}
